I have this application where I want to deploy some web interface to a syslog server.
So the syslogserver does write his stuff into a mysql database. I already have build
some parts for the application except for this specific part where I want to build a dropdown select form, to select the hosttables inside the database.
Actually I am using flask, flask-sqlalchemy and wtforms. So I tried to implement this over the 'QuerySelectField', somehow I only get a dropdown with no table name entries shown.
I should mention that the tables inside the database itself are created on the fly. For my model I used the automap_base() Feature from sqlalchemy:
model.py
Base = automap_base()
engine = create_engine("mysql://sumuser:tehpass#127.0.0.1/syslog")
Base.prepare(engine, reflect=True)
session = Session(engine)
This is whats inside my forms:
forms.py
def factoryHelper():
return session.query("information_schema.tables.table_name from information_schema.tables where information_schema.tables.table_name like 'messages_hostname0'")
class HostSelectForm(Form):
title = TextField('Fooblah')
hostTables = QuerySelectField(query_factory=factoryHelper,allow_blank=True)
and this inside the views:
views.py
#app.route('/index/', defaults={'page':1})
#app.route('/index/page/<int:page>')
def index(page):
form = HostSelectForm()
count = session.execute("select host,facility,level,msg from messages_hostname0").rowcount
pagination = Pagination(page, PER_PAGE, count)
return render_template('index.html', pagination=pagination, form=form)
So is there anyway I can create a dropdown menu from dynamically created table names? Also if I use the automap feature? Thanks in advance.
Somehow i managed to solve this issue with this in the model.py:
def reflectTables():
for i in Base.classes.items():
yield i[0]
def stripTables():
tablelist = []
datelist = []
re0 = r"^(?P<prefix>[a-zA-Z]*)\_(?P<mid>[a-zA-Z,0-9]*)\_(?P<suffix>[0-9]*)"
myre = re.compile(re0, re.MULTILINE)
for x,table in enumerate(reflectTables()):
striptablename = re.match(myre, table.strip("\n"))
if striptablename:
tablelist.append((x, striptablename.group(2)))
datelist.append((x, striptablename.group(3)))
return dicht(tablelist,datelist)
the forms.py:
AVAILABLE_CHOICES = stripTables()
class HostSelectForm(Form):
tableSelect = SelectField('LogHost', choices=AVAILABLE_CHOICES, default=0)
and finnaly inside the views.py:
if request.method == "GET":
count = session.query("* from mytable_monitory_counts")
items = session.execute("select * from mytable_%s_%s limit %s, %s" % \
(tableslector[int(request.values['tableSelect'])][1],\
datelist[int(request.values['tableSelect'])][1], my_start_range, PER_PAGE)).fetchall()
pagination = Pagination(page=page, total=count.count(), search=search, record_name='hosts')
if not items and page != 1:
in_error()
return render_template('index.html', pagination=pagination, form=form, items=items)
Related
I'm working on a project using Flask and a PostgreSQL database, with SQLAlchemy.
I have Group objects which have a list of User IDs who are members of the group. For some reason, when I try to add an ID to a group, it will not save properly.
If I try members.append(user_id), it doesn't seem to work at all. However, if I try members += [user_id], the id will show up in the view listing all the groups, but if I restart the server, the added value(s) is (are) not there. The initial values, however, are.
Related code:
Adding group to the database initially:
db = SQLAlchemy(app)
# ...
g = Group(request.form['name'], user_id)
db.session.add(g)
db.session.commit()
The Group class:
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy.dialects.postgresql import ARRAY
class Group(db.Model):
__tablename__ = "groups"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
leader = db.Column(db.Integer)
# list of the members in the group based on user id
members = db.Column(ARRAY(db.Integer))
def __init__(self, name, leader):
self.name = name
self.leader = leader
self.members = [leader]
def __repr__(self):
return "Name: {}, Leader: {}, Members: {}".format(self.name, self.leader, self.members)
def add_user(self, user_id):
self.members += [user_id]
My test function for updating the Group:
def add_2_to_group():
g = Group.query.all()[0]
g.add_user(2)
db.session.commit()
return redirect(url_for('show_groups'))
Thanks for any help!
As you have mentioned, the ARRAY datatype in sqlalchemy is immutable. This means it isn’t possible to add new data into array once it has been initialised.
To solve this, create class MutableList.
from sqlalchemy.ext.mutable import Mutable
class MutableList(Mutable, list):
def append(self, value):
list.append(self, value)
self.changed()
#classmethod
def coerce(cls, key, value):
if not isinstance(value, MutableList):
if isinstance(value, list):
return MutableList(value)
return Mutable.coerce(key, value)
else:
return value
This snippet allows you to extend a list to add mutability to it. So, now you can use the class above to create a mutable array type like:
class Group(db.Model):
...
members = db.Column(MutableList.as_mutable(ARRAY(db.Integer)))
...
You can use the flag_modified function to mark the property as having changed. In this example, you could change your add_user method to:
from sqlalchemy.orm.attributes import flag_modified
# ~~~
def add_user(self, user_id):
self.members += [user_id]
flag_modified(self, 'members')
To anyone in the future: so it turns out that arrays through SQLAlchemy are immutable. So, once they're initialized in the database, they can't change size. There's probably a way to do this, but there are better ways to do what we're trying to do.
This is a hacky solution, but what you can do is:
Store the existing array temporarily
Set the column value to None
Set the column value to the existing temporary array
For example:
g = Group.query.all()[0]
temp_array = g.members
g.members = None
db.session.commit()
db.session.refresh(g)
g.members = temp_array
db.session.commit()
In my case it was solved by using the new reference for storing a object variable and assiging that new created variable in object variable.so, Instead of updating the existing objects variable it will create a new reference address which reflect the changes.
Here in Model,
Table: question
optional_id = sa.Column(sa.ARRAY(sa.Integer), nullable=True)
In views,
option_list=list(question.optional_id if question.optional_id else [])
if option_list:
question.optional_id.clear()
option_list.append(obj.id)
question.optional_id=option_list
else:
question.optional_id=[obj.id]
I am trying to create a form for editing a record in a table.
Here is a brief sample of my Interface:
class ICalibration(Interface):
"""Interface class for Calibration
"""
Calibration_ID = schema.Int(title=u"Calibration_ID",
required=False
)
...
Calibration_Type = schema.Choice(title=u"Calibration Type",
description=u"Type of Calibration",
source=_calibrationTypes,
required=True,
)
...
Last_Calibration = schema.Datetime(title=u"Last Calibration",
required=False,
)
...
Here is a brief sample of the ORMBase class:
#implementer(ICalibration)
class Calibration(ORMBase)
__tablename__="Calibrations"
Calibration_ID = sqlalchemy.Column(sqlalchemy.Integer(),
primary_key=True,
autoincrement=True
)
Calibration_Type = sqlalchemy.Column(sqlalchemy.Integer(),
nullable=False
)
...
Last_Calibration = sqlalchemy.Column(sqlalchemy.types.DateTime(),
nullable=True,
)
...
Here is what I have so for the form class:
class EditCalibration(form.SchemaForm):
grok.name('edit-calibration')
grok.require('zope2.View')
grok.context(ISiteRoot)
schema = ICalibration
id = None
#memoize
def getContent(self):
self.id = self.request.get('id',None)
if self.id:
return session.query(Calibration).filter(Calibration.Calibration_ID == self.id).one()
#button.buttonAndHandler(u'Submit')
def handleOk(self, action):
data, errors = self.extractData()
#do something with data
....
#button.buttonAndHandler(u'Cancel')
def handleCancel(self, action):
#redirect user
....
The values themselves do show up properly in their respective fields, but I am getting the error in the debug menu:
TypeError: ('Could not adapt', None, <InterfaceClass gpcl.calibration.calibration.ICalibration>)
How can I fix this problem so that it adapts correctly?
Also, for reference, I actually asked a question before that got an answer providing code for the Calibration and CalibrationForm classes. Because the fields fill out fine, there is probably something I did wrong.
I've got a model method that calculates the score of votes using the HN ranking algorithm in my model that also caches each score. calculate_score() is the main thing to focus on here.
class Submission(models.Model):
submission_type = models.CharField(_('Submission Type'),
max_length=255,
choices=tuple([(media.name.lower(), media.value) for media in MediaTypes]),
blank=False)
title = models.CharField(_('Title'), max_length=100, blank=False)
url = models.URLField(_('Link'), blank=False)
description = models.TextField(_('Description'))
flagged = models.BooleanField(_('Is Flagged for Review'), default=False)
user = models.ForeignKey(User, related_name='user_submissions')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
by_votes = VoteCountManager()
objects = models.Manager()
def __str__(self):
return self.submission_type + ': ' + self.title
def get_absolute_url(self):
return reverse('submit_detail', args=[
str(self.submission_type),
str(self.id),
])
def get_vote(self):
"""
Returns the number of votes associated with a particular submission
:return: int
"""
return self.submission_votes.count()
def calculate_score(self):
"""
This is a variation of the HN ranking algorithm
:return: score
"""
secs_in_hour = float(60 * 60)
g = 1.2
delta = timezone.now() - self.date_submitted
item_hour_age = delta.total_seconds() / secs_in_hour
votes = self.submission_votes.count() - 1
score = votes / pow((item_hour_age + 2), g)
cached = cache.get('s{0}'.format(self.pk))
if not cached:
cache.set('s{0}'.format(self.pk), score, 30)
return score
I'm using the djangorestframework that serializes my model:
class SubmissionSerializer(serializers.HyperlinkedModelSerializer):
score = serializers.SerializerMethodField('rank')
class Meta:
model = Submission
def rank(self, obj):
return obj.calculate_score()
I have two ways that I think I can solve my problem, but I don't know how to do either of them, and I'm not sure which one is best. Since I'm caching the score for each individual submission. I was trying to order the submissions in a ListView like this pulling from the cache:
class SubmissionList(ListView):
model = Submission
def get_queryset(self):
return super(SubmissionHotList, self).get_queryset().annotate(
score=cache.get('s{0}'.format(x) for x in Submission.pk)
votes=models.Count('submission_votes'),
).order_by(
'-score', '-votes',
)
But I found that order_by only works on the database level, and I would have to create a database field for the calculated score, which I would like to avoid if possible. My other possibility is using the serialized data in my API for Submission in this ListView, but I'm not sure if APIs are only there for external applications, or if I could use them in the same application that the API is generated.
I guess my question is. Would it be better to get the cached objects to list each submission in a particular order, or could I use the API to accomplish this? And if I used the API, how could I parse and order the JSON data in the ListView, while keeping votes as a secondary ordering mechanism?
if you want to display all items without pagination, you will not lose any performance if you do something like
import operator
class SubmissionList(TemplateView):
template_name = "submissions.html"
def get_context_data(self, **kwargs):
context = super(SubmissionList, self).get_context_data(**kwargs)
submissions = Submission.objects.all().annotate(
votes=models.Count('submission_votes')
)
sorted_submissions = sorted(submissions, key=lambda x:(cache.get('s{0}'.format(x.pk)), x.votes), reverse=True)
context['submissions'] = sorted_submissions
return context
I have worked on this for sometime now and I have decided to seek for your help. I have an ongoing project and I am using the django framework to build the site.
I have an html page that requests for user input, I have been able to extract the given data using the form.cleaned_data['']. A
#This is my views.py;
from django.shortcuts import render_to_response
from django.template import RequestContext
from forms import CarloanForm
def index(request):
form = CarloanForm()
if request.POST:
form = CarloanForm(request.POST)
if form.is_valid():
form.save()
else:
form = CarloanForm()
return render_to_response('carloan/index.html', {'form': form},
context_instance=RequestContext(request))
#I am extracting the user inputs using;
amount_of_vehicle = form.cleaned_data['cost_of_vehicle_Naira']
tenure = form.cleaned_data['loan_repayment_tenure_Months']
interest_rate = form.cleaned_data['interest_rate_Percentage']
equity = form.cleaned_data['equity_contrib_rate_Percentage']
depreciation_rate = form.cleaned_data['depreciation_rate_Percentage']
What I need to do and don't know how to go about it are;
Carry out some operations on the data (extracted using form.cleaned_data['']) and I have written some lines of code to that effect;
class LoanCalc:
def __init__(self,amount_of_vehicle,tenure,interest_rate,equity,depreciation_rate):
self.amount_of_vehicle = float(amount_of_vehicle)
self.tenure = float(tenure)
self.interest_rate = float(interest_rate)
self.equity = float(equity)
self.depreciation_rate = float(depreciation_rate)
def interest(self):
return (self.interest_rate/100) * self.amount_of_vehicle *(self.tenure/12)
def management_fee(self):
return 0.01 * (self.amount_of_vehicle + user.interest())
def processing_fee(self):
return 0.0025 *(self.amount_of_vehicle + user.interest())
def legal_fee(self):
return 0.0075 * (self.amount_of_vehicle + user.interest())
def residual_amount(self):
return 0.01 * (self.amount_of_vehicle - ((self.depreciation_rate/100) * self.amount_of_vehicle *(self.tenure/12)))
def equity_contribution(self):
return (self.equity/100) * self.amount_of_vehicle
def LoanPaymentPlan(self):
months = 1
total_amount = self.amount_of_vehicle+user.interest()+user.management_fee()+user.processing_fee()+user.legal_fee()+user.residual_amount()
upfront_payment = user.management_fee()+user.processing_fee()+user.legal_fee()+user.equity_contribution()+user.residual_amount()
opening_balance = total_amount - upfront_payment
balance = opening_balance
while months <= self.tenure:
if balance > 0:
monthly_instalment =(opening_balance/self.tenure)
monthly_interest = (((self.interest_rate/100) * balance)/ 12)
loan_payment = monthly_instalment - monthly_interest
closing_balance = balance - monthly_instalment
print' ',months,' ',round(balance,2),' ', round(monthly_instalment,2),' ',round(monthly_interest,2) \
, ' ',' ',round(loan_payment,2),' ',round(closing_balance,2)
balance = closing_balance
months += 1
return 'Thank you for using the Loan Calculator App'
and i want to carry out the operations in the code above on the extracted data.
I am thinking of doing it in such a way like this;
Create an empty dictionary;
user = {}
user = LoanCalc(amount_of_vehicle,tenure,interest_rate,equity,depreciation_rate)
result= user.interest()
result1 = user.management_fee()
. .
. .
. .
result10 = user.LoanPaymentPlan()
Pass the result(s) obtained from (question 2) to the same template that generated the form.
Please help me out guys, i am still very new to django. Thanks
This is the full stack of the referenced error am getting:
Environment:
Request Method: GET
Request URL: http://127.0.0.1:9000/result/
Django Version: 1.4.1
Python Version: 2.5.4
Installed Applications:
('django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'carloan')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware')
Traceback:
File "C:\Python25\Lib\site-packages\django\core\handlers\base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "C:\Users\AJIBIKE\Documents\Circuit Atlantic\calculator\carloan\views.py" in result_page
226. 'carloan': instance,
def result_page(request):
if request.POST:
form = Carloan_formForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.interest = (instance.interest_rate/100) * instance.amount_of_vehicle * (instance.tenure/12)
instance.save()
else:
form = Carloan_formForm()
return render_to_response('carloan/result.html', {'carloan': instance,'form': form},
context_instance=RequestContext(request))
Exception Type: UnboundLocalError at /result/
Exception Value: local variable 'instance' referenced before assignment
Latest
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect, HttpResponse
from forms import Carloan_formForm
def index(request):
if request.POST:
form = Carloan_formForm(request.POST)
if form.is_valid():
instance = form.save()
return HttpResponseRedirect ('/result/')
form = Carloan_formForm()
kwvars = {
'form': form,
}
return render_to_response('carloan/index.html', kwvars,
context_instance=RequestContext(request))
def result_page(request):
instance = None
if request.POST:
form = Carloan_formForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.interest = (instance.interest_rate_Percentage/100) * instance.cost_of_vehicle_Naira * (instance.loan_repayment_tenure_Months/12)
instance.management_fee = 0.01 * (instance.cost_of_vehicle_Naira + instance.interest)
instance.processing_fee = 0.0025 * (instance.cost_of_vehicle_Naira + instance.interest)
instance.legal_fee = 0.0075 * (instance.cost_of_vehicle_Naira + instance.interest)
#i get syntax error starting from this line, and when i comment it out and the lines below, there is no syntax error.
instance.residual_amount = 0.01 * ((instance.cost_of_vehicle_Naira - ((instance.depreciation_rate_Percentage/100) * instance.cost_of_vehicle_Naira * (instance.tenure/12)))
instance.equity_contribution = (instance.equity_contrib_rate_Percentage/100) * instance.cost_of_vehicle_Naira)
instance.save()
else:
form = Carloan_formForm()
return render_to_response('carloan/result.html', {'instance': instance, 'form': form},
context_instance=RequestContext(request))
forms.py
from django import forms
from django.forms import ModelForm
from models import Carloan_form
class Carloan_formForm(ModelForm):
class Meta:
model = Carloan_form
exclude = ('interest', 'management_fee', 'processing_fee', 'legal_fee', \
'residual_amount', 'equity_contribution')
1 Save the result of the operations carried out on the extracted data in the Django Administration. (N.B: I already know how to save the user input)
Create a model to save the user's entries. app/models.py
class Carloan(models.Model) :
# In addition to your model fields add the functions that are part of your LoanCalc
...
Then create a ModelForm: app/forms.py
class CarLoanForm(forms.ModelForm) :
class Meta:
model = Carloan
Use CarLoanForm the same way you normally would except: app/views.py
if request.POST:
form = CarloanForm(request.POST)
if form.is_valid():
carloan = form.save()
#pass carloan (the instance) to the template and
#call its functions(from LoanCalc) instead of
#passing all of the values separately
Lastly, register this Model so it appears in the admin section app/admin.py
from django.contrib import admin
from app.models import Carloan
admin.sites.register(Carloan)
2 I have divided the page into two(one for the form and the other for
the result) and i want to pass the result to one-half of the page to
enable the user to see it.
Using the steps above, carloan will be passed to the view. go ahead and render its values.
Additionally:
Refactor index
your index definition should be refactored slightly, or your POST bound form will never make it to the template:
def index(request):
if request.POST:
form = CarloanForm(request.POST)
if form.is_valid():
instance = form.save()
else :
form = CarloanForm()
return render_to_response('carloan/index.html', {'form': form},
context_instance=RequestContext(request))
Refactor your ModelForm
Carry out some operations on the data (extracted using form.cleaned_data['']) and I have written some lines of code to that effect; - using a ModelForm instead of a regular django form you will get an instance of the model with the user's values already filled in, you reference them with instance.field.
and i want to carry out the operations in the code above on the extracted data. - the fields that you plan to calculate values for are listed in exclude they will not factor into form validation.
class CarLoanForm(forms.ModelForm) :
class Meta:
model = Carloan
# exclude fields that are calculated from user input
# NOTE: these fields must be in your model
exclude = ('interest', 'management_fee'...)
Refactor result page
Carry out some operations on the data (extracted using form.cleaned_data['']) and I have written some lines of code to that effect;
in results_page under the form.is_valid check, ther is a line: instance = form.save(commit=False) this gets all of the values that the user submitted in a instance of the Carloan Model that has NOT been saved yet.
further down: instance.interest = (instance.interest_rate/100) * instance.amount_of_vehicle *(instance.tenure/12) in this line I am calculating one of the excluded fields values (this calculation is a copy of your code).
lastly (after all of the operations on the data have been completed) I save the instance of the model.
then pass the instance to the template for display.
code:
def result_page(request):
if request.POST:
form = CarloanForm(request.POST)
if form.is_valid():
# get an instance from the form but don't save it
instance = form.save(commit=False)
# calculate excluded field values
instance.interest = (instance.interest_rate/100) * instance.amount_of_vehicle *(instance.tenure/12)
...
# after you have calculated all of the excluded fields save the instance
instance.save()
else :
form = CarloanForm()
return render_to_response('carloan/result.html', {'carloan' : instance, 'form' : form},
context_instance=RequestContext(request))
Error:
reference before error assignment - What does it say was referenced before assignment? Paste the entire stack trace please.
One of the simpler ways would be to store the data in the session in one view, retrieve it in the next. The docs will help.1
Having said that - there are a few other ways to approach the problem. It has been discussed more than once on SO:
Django Passing data between views
How do you pass or share variables between django views?
how to pass a list between views in django
I defined some WTForms forms in an application that uses SQLALchemy to manage database operations.
For example, a form for managing Categories:
class CategoryForm(Form):
name = TextField(u'name', [validators.Required()])
And here's the corresponding SQLAlchemy model:
class Category(Base):
__tablename__= 'category'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255))
def __repr__(self):
return '<Category %i>'% self.id
def __unicode__(self):
return self.name
I would like to add a unique constraint on the form validation (not on the model itself).
Reading the WTForms documentation, I found a way to do it with a simple class:
class Unique(object):
""" validator that checks field uniqueness """
def __init__(self, model, field, message=None):
self.model = model
self.field = field
if not message:
message = u'this element already exists'
self.message = message
def __call__(self, form, field):
check = self.model.query.filter(self.field == field.data).first()
if check:
raise ValidationError(self.message)
Now I can add that validator to the CategoryForm like this:
name = TextField(u'name', [validators.Required(), Unique(Category, Category.name)])
This check works great when the user tries to add a category that already exists \o/
BUT it won't work when the user tries to update an existing category (without changing the name attribute).
When you want to update an existing category : you'll instantiate the form with the category attribute to edit:
def category_update(category_id):
""" update the given category """
category = Category.query.get(category_id)
form = CategoryForm(request.form, category)
The main problem is I don't know how to access the existing category object in the validator which would let me exclude the edited object from the query.
Is there a way to do it? Thanks.
In the validation phase, you will have access to all the fields. So the trick here is to pass in the primary key into your edit form, e.g.
class CategoryEditForm(CategoryForm):
id = IntegerField(widget=HiddenInput())
Then, in the Unique validator, change the if-condition to:
check = self.model.query.filter(self.field == field.data).first()
if 'id' in form:
id = form.id.data
else:
id = None
if check and (id is None or id != check.id):
Although this is not a direct answer I am adding it because this question is flirting with being an XY Problem. WTForms primary job is to validate that the content of a form submission. While a decent case could be made that verifying that a field's uniqueness could be considered the responsibility of the form validator, a better case could be made that this is the responsibility of the storage engine.
In cases where I have be presented with this problem I have treated uniqueness as an optimistic case, allowed it to pass form submission and fail on a database constraint. I then catch the failure and add the error to the form.
The advantages are several. First it greatly simplifies your WTForms code because you do not have to write complex validation schemes. Secondly, it could improve your application's performance. This is because you do not have to dispatch a SELECT before you attempt to INSERT effectively doubling your database traffic.
The unique validator needs to use the new and the old data to compare first before checking if the data is unique.
class Unique(object):
...
def __call__(self, form, field):
if field.object_data == field.data:
return
check = DBSession.query(model).filter(field == data).first()
if check:
raise ValidationError(self.message)
Additionally, you may want to squash nulls too. Depending on if your truly unique or unique but allow nulls.
I use WTForms 1.0.5 and SQLAlchemy 0.9.1.
Declaration
from wtforms.validators import ValidationError
class Unique(object):
def __init__(self, model=None, pk="id", get_session=None, message=None,ignoreif=None):
self.pk = pk
self.model = model
self.message = message
self.get_session = get_session
self.ignoreif = ignoreif
if not self.ignoreif:
self.ignoreif = lambda field: not field.data
#property
def query(self):
self._check_for_session(self.model)
if self.get_session:
return self.get_session().query(self.model)
elif hasattr(self.model, 'query'):
return getattr(self.model, 'query')
else:
raise Exception(
'Validator requires either get_session or Flask-SQLAlchemy'
' styled query parameter'
)
def _check_for_session(self, model):
if not hasattr(model, 'query') and not self.get_session:
raise Exception('Could not obtain SQLAlchemy session.')
def __call__(self, form, field):
if self.ignoreif(field):
return True
query = self.query
query = query.filter(getattr(self.model,field.id)== form[field.id].data)
if form[self.pk].data:
query = query.filter(getattr(self.model,self.pk)!=form[self.pk].data)
obj = query.first()
if obj:
if self.message is None:
self.message = field.gettext(u'Already exists.')
raise ValidationError(self.message)
To use it
class ProductForm(Form):
id = HiddenField()
code = TextField("Code",validators=[DataRequired()],render_kw={"required": "required"})
name = TextField("Name",validators=[DataRequired()],render_kw={"required": "required"})
barcode = TextField("Barcode",
validators=[Unique(model= Product, get_session=lambda : db)],
render_kw={})
Looks like what you are looking for can easily be achieved with ModelForm which is built to handle forms that are strongly coupled with models (the category model in your case).
To use it:
...
from wtforms_components import Unique
from wtforms_alchemy import ModelForm
class CategoryForm(ModelForm):
name = TextField(u'name', [validators.Required(), Unique(Category, Category.name)])
It will verify unique values while considering the current value in the model. You can use the original Unique validator with it.
This worked for me, simple and easy:
Make sure that every time when a new row created in DB it must have unique name in colomn_name_in_db otherwise it will not work.
class SomeForm(FlaskForm):
id = IntegerField(widget=HiddenInput())
fieldname = StringField('Field name', validators=[DataRequired()])
...
def validate_fieldname(self, fieldname):
names_in_db = dict(Model.query.with_entities(Model.id,
Model.colomn_name_in_db).filter_by(some_filtes_if_needed).all())
if fieldname.data in names_in_db.values() and names_in_db[int(self.id)] != fieldname.data:
raise ValidationError('Name must be unique')