Django form performance - mysql

I asked this in Code Review but it was rejected with a cause of "broken code." That is why I'm asking it here. This site is probably more appropriate for this question than the Code Review one.
In my app, a user can modify a course that they created. One field is a "teacher" field and the user can select a different person to be the teacher. This ForeignKey creates 138 duplicated queries and I can't figure out how to make it more efficient.
Model:
class CourseCatalog(models.Model):
course_name = models.CharField(verbose_name="Course name", max_length=50)
course_desc = models.TextField(verbose_name="Course Description")
teacher = models.ForeignKey(Teacher, blank=True, null=True,
verbose_name='Course Owner', on_delete=models.PROTECT)
...
View:
class EditCourseCatalog(UpdateView):
model = CourseCatalog
fields = ['course_name','course_desc', 'teacher']
template_name = 'school/course_catalog/new_edit_form.html'
Template:
...
<h3>Course Form</h3>
{{ user.teacher }}
<form action="" method="post">{% csrf_token %}
{{form|crispy}}
...
Here is the query from debug that is duplicated 138 times. The only difference between the queries is the school_familymember.id = 220.
SELECT `school_familymember`.`id`, `school_familymember`.`password`,
school_familymember.last_login, school_familymember.is_superuser,
school_familymember.username, school_familymember.first_name,
school_familymember.last_name, school_familymember.email.school_familymember.is_staff, school_familymember.is_active, school_familymember.date_joined, school_familymember.family_id, school_familymember.middle_name, school_familymember.family_member_role_id, school_familymember.address1, school_familymember.address2, school_familymember.city, school_familymember.state, school_familymember.zip_code, school_familymember.notes, school_familymember.gender, school_familymember.phone_number, school_familymember.cell_phone_number FROM school_familymember WHERE school_familymember.id = 220
The Teacher model is also a foreign key to the FamilyMember table and this is where I think I'm having the issue. I'm wondering if there is a way to make one single query to collect the family names and ids and then use that for the drop down list in the form. Can I do this with the built in form managers or do I have to scrap that and create the queries in the view and pass them to the form?
class Teacher(models.Model):
family_member = models.OneToOneField(FamilyMember, verbose_name='name')
notes = models.TextField(blank=True)

Create a custom model form, and in the __init__ method change the teachers queryset to use select_related to be more efficient.
class CourseCatalogForm(forms.ModelForm):
class Meta:
fields = ['course_name','course_desc', 'teacher']
def __init__(self, *args, **kwargs):
super(CourseCatalogForm, self).__init__(*args, **kwargs)
self.fields['teacher'].queryset = self.fields['teacher'].queryset.select_related('family_member')
Then use your new model form class in your view instead of specifying fields.
class EditCourseCatalog(UpdateView):
model = CourseCatalog
template_name = 'school/course_catalog/new_edit_form.html'
form_class = CourseCatalogForm

Related

How do I populate my form fields with data from the database Django

Hello I have a form page and I only need users to fill in certain fields, with the rest of the fields being pre-filled for them based on the module they pick.
While I can fetch the objects from my database -- i.e. the dropdown list shows Module Object (1), Module Object (2) -- I need only certain fields in these objects, which is why this similar sounding post couldn't answer my question:
Populate a django form with data from database in view
Here's my forms.py
class inputClassInformation(forms.Form):
module = forms.ModelChoiceField(queryset=Module.objects.all())
duration = forms.CharField(disabled=True, required=False)
description = forms.CharField()
assigned_professors = forms.ModelChoiceField(queryset=Class.objects.filter(id='assigned_professors'))
models.py -- not the full models are shown to reduce the post's length
class Module(models.Model):
subject = models.CharField(max_length=200, default="")
class Class(models.Model):
module = models.ForeignKey(Module, on_delete=models.CASCADE, default="")
duration = models.CharField(max_length=200, default="")
description = models.CharField(max_length=200, default="")
assigned_professors = models.CharField(max_length=200, default="")
So an expected result would be:
1) The Module field shows the subjects, instead of Module objects in its dropdown list and
2) The duration field is automatically filled in for the user, based on the module they picked. The reason is so that the user only has to manually fill in certain fields, while the rest are automatically generated.
This has had me stuck for a long while, help is appreciated. Thanks!
So an expected result would be:
1) The Module field shows the subjects, instead of Module objects in its dropdown list and
2) The duration field is automatically filled in for the user.
These are essentially two different questions.
To answer the first: you can override the:
__str__ method for your Model class for python 3 and django 1.8) and the
__unicode__ method for your Model class for django <1.8 and not python3.
For example, to make subjects appear instead of "XXXX Object" for your class do:
class Module(models.Model):
subject = models.CharField(max_length=200, default="")
def __unicode__(self):
return self.subject
Similarly, change __unicode__ for __str__ as appropriate for your django version.
Now, to answer your second question:
2) The duration field is automatically filled in for the user.
You need to do two things:
Do not display the duration field in your form (unless you want to give the users the chance to sometimes fill it in manually)
Override the save method
An example:
class Class(models.Model):
module = models.ForeignKey(Module, on_delete=models.CASCADE, default="")
duration = models.CharField(max_length=200, default="")
description = models.CharField(max_length=200, default="")
assigned_professors = models.CharField(max_length=200, default="")
def save(self, *args, **kwargs):
self.duration = #The value you'd want to automatically set here#
super(Model, self).save(*args, **kwargs)

Django CheckboxSelectMultiple widget : render only selected data by default

Greeting, I have a manytomany field call user in my model_A model, in my form, how can I display only the list of selected data associated to the model_A by default instead of listing entire entries from the User model in my html page? my intention is to create a setting page where I can remove the user associated to a project
below is my code :
model.py :
class model_A(models.Model):
user = models.ManyToManyField(User, blank=True)
form.py :
class EditForm(forms.ModelForm):
prefix = 'edit_form'
class Meta:
model = model_A
fields = '__all__'
widgets = {'user':forms.CheckboxSelectMultiple}
html :
<div class="field">
{{form.user}}
</div>
Any help is much appreciated thanks
Change user queryset inside __init__ method of EditForm class.
class EditForm(forms.ModelForm):
prefix = 'edit_form'
class Meta:
model = model_A
fields = '__all__'
widgets = {'user':forms.CheckboxSelectMultiple}
def __init__(self, **kwargs):
self.event = kwargs.pop('event')
super().__init__(**kwargs)
# replace dots with your conditions in filter
self.fields['user'].queryset = self.user.filter(...)
UPDATE
List users that are associated with Model_A
self.fields['user'].queryset = self.user.all()

Django-Filter Form Displaying All Filters?

I've just started working with Django Filter. When I test, the filter.form shows filters for all fields and I can't get it to show only the desired filters.
Here's the filter:
class EmployeeFilter(django_filters.FilterSet):
hire_date = django_filters.DateFilter(name='hireDate', lookup_expr='hireDate__year')
hire_date__gte = django_filters.DateFilter(name='hireDate', lookup_expr='hireDate__gte')
hire_date__lte = django_filters.DateFilter(name='hireDate', lookup_expr='hireDate__lte')
class Meta:
model = models.Employee
fields=['hireDate']
Here's the view:
def test_filters(request, template_name='filter-test.html'):
from . import filters
f = filters.EmployeeFilter(request.GET, queryset=models.Employee.objects.all())
return render_to_response(template_name, locals(), context_instance=RequestContext(request))
Has anyone ever run into this? How'd you fix?
I had the same issue but I was able to solve
class EmployeeFilter(django_filters.FilterSet):
class Meta:
model = Employee
fields=['hireDate']
In Views.py
def filters(request):
employee = Employee.objects.all()
myFilter = EmpoyeeFilter(request.GET, queryset=employee)
employee = myFilter.qs
context = {
'myFilter': myFilter,
'employee': employee,
}
return render(request, 'templates/index.html', context)
index.html file
<html>
<form method="get">
{{myFilter.form}}
</form>
</html>
The filter set's form plays both of Django Form's two roles:
It enables you to display the form in your template
It validates the incoming values.
The second of these is (arguably) the more important — certainly for Django Filter: if you remove fields from the form they will not be filtered against.
Your best bet is probably to just define less fields on the filter set. (If you need all fields in some cases just define two filter sets, one for each need.)
(The other option would be to define a separate Django Form with just the fields you need and use that in your template, leaving the filter set's form to do the actual validation.)
I hope that helps.
models.py
class Employee(models.Model):
hire_date = models.DateField()
name = models.CharField(max_length=128)
filters.py
class EmployeeFilter(django_filters.FilterSet):
hire_date = django_filters.DateFilter(field_name='hire_date', lookup_expr='gt')
hire_date__gte = django_filters.DateFilter(field_name='hire_date', lookup_expr='gt')
hire_date__lte = django_filters.DateFilter(field_name='hire_date', lookup_expr='gt')
name__icontains = django_filters.CharFilter(field_name='name', lookup_expr='icontains')
class Meta:
model = Employee
fields = {}
views.py
def employee_view(request):
f = EmployeeFilter(request.GET, queryset=Employee.objects.all())
return render(request, 'product/employee.html', {'filter': f})
templates/my-app/employee.html
<h1>Employee</h1>
<form method="get">
{{ filter.form.as_p }}
<input type="submit"/>
</form>
<ul>
{% for employee in filter.qs %}
{{ employee.hire_date }}<br/>
{% endfor %}
</ul>
The field_name specifies the name of the field in the model ie Employee and lookup_expr is a string which specifies the filter to use on the column of table for eg gt, gte, lt, lte, exact, iexact, contains, icontains
If you want to filter against a field, but do not want it in the form, you can do the following:
import django_filters
from django import forms
class MyFilter(django_filters.FilterSet):
field = django_filters.CharFilter(
widget=forms.HiddenInput()
)
class Meta:
model = MyModel
fields = ['other_field', 'field']
The only downside is, it will show as an empty parameter in the URL, when you submit the form. But it won't affect your results, since its an empty value.

Unique validator in WTForms with SQLAlchemy models

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')

How to specify an association relation using declarative base

I have been trying to create an association relation between two tables, intake and module . Each intake has a one-to-many relationship with the modules.
However there is a coursework assigned to each module, and each coursework has a duedate which is unique to each intake.
I tried this but it didnt work:
intake_modules_table = Table('tg_intakemodules',metadata,
Column('intake_id',Integer,ForeignKey('tg_intake.intake_id',
onupdate="CASCADE",ondelete="CASCADE")),
Column('module_id',Integer,ForeignKey('tg_module.module_id',
onupdate ="CASCADE",ondelete="CASCADE")),
Column('dueddate', Unicode(16))
)
class Intake(DeclarativeBase):
__tablename__ = 'tg_intake'
#{ Columns
intake_id = Column(Integer, autoincrement=True, primary_key=True)
code = Column(Unicode(16))
commencement = Column(DateTime)
completion = Column(DateTime)
#{ Special methods
def __repr__(self):
return '"%s"' %self.code
def __unicode__(self):
return self.code
#}
class Module(DeclarativeBase):
__tablename__ ='tg_module'
#{ Columns
module_id = Column(Integer, autoincrement=True, primary_key=True)
code = Column(Unicode(16))
title = Column(Unicode(30))
#{ relations
intakes = relation('Intake',
secondary=intake_modules_table, backref='modules')
#{ Special methods
def __repr__(self):
return '"%s"'%self.title
def __unicode__(self):
return '"%s"'%self.title
#}
When I do this the column duedate specified in the intake_module_table is not created.
Please some help will be appreciated here.
thanks in advance
Actually column duedate is created, but you don't get it as some model attribute when querying your models. I you need to define intermediate model for intake_modules_table table and setup relation to it instead of Intake. Sure, the access to columns of relation will be a bit longer (module.infakes[0].duedate, module.infakes[0].infake.code). Also you can setup association proxy to access list of Infake objects the same way you do now.