Django Custom field's attributes making database queries - database

I am facing a very weird problem in one of my django projects. In my project I have a custom field class that handles foreign keys, one to one and many 2 many model fields. The class is some thing like the following.
from django import forms
class CustomRelatedField(forms.Field):
def __init__(self, model, limit=None, multiple=False, create_objects=True, *args, *kwargs):
self.model = model
self.limit = limit
self.multiple = multiple
self.create_objects = create_objects
super(CustomRelatedField, self).__init__(*args, **kwargs)
def clean(self, value):
""" Calls self.get_objects to get the actual model object instance(s)
from the given unicode value.
"""
# Do some value processing here
return self.get_objects(value)
def get_objects(self, values):
""" Returns the model object instances for the given unicode values.
"""
results = []
for value in values:
try:
obj = self.model.object.get_or_create(name=value)[0]
results.append(obj)
except Exception, err:
# Log the error here.
return results
def prepare_value(self, value):
""" Returns the value to be sent to the UI. The value
passed to this method is generally the object id or
a list of object id's (in case it is a many to many object).
So we need to make a database query to get the object and
then return the name attribute.
"""
if self.multiple:
result = [obj.name for obj in self.model.filter(pk__in=value)]
else:
result = self.model.object.get(pk=value)
return result
Recently while I was playing with the django-toolbar, I found out one of the pages that has a form with the above mentioned fields was ridiculously making multiple queries for the same objects again and again.
While debugging, I found out the prepare_value method was being called again and again. After some more debugging, I realized the culprit was the template. I have a generic template that I use for forms, It looks something like the following:
{% for field in form %}
{% if field.is_hidden %}
<!-- Do something here -->
{% endif %}
{% if field.field.required %}
<!-- Do something here -->
{% endif %}
<label>{{ field.label }}</label>
<div class="form-field">{{ field }}</div>
{% if field.field.widget.attrs.help_text %}
<!-- Do something here -->
{% elif field.errors %}
<!-- Do something here -->
{% endif %}
{% endfor %}
In the above code, each if statement calls the field class which calls the prepare_value method which then makes the database queries. Each of the following listed is making a database query, I am totally lost to why this is happening and have no clue about any solutions. Any help, suggestions would be really appreciated. Thanks.
field.is_hidden
field.field.required
field.label
field.label_tag
field
field.field.widget.attrs.help_text
field.errors
Also, why does this happen with my custom field class only, other fields (FKs, O2Os, M2M's) in the application and the application admin, just make one query, even though they are using a similar template.

Problem is with your prepare_value() method which does explicit queries. .get() does not get cached and always hits the db while iterating on .filter() queryset will evaluate that.
This might be causing you multiple queries.
This is not seen in default fields because they do not do any queries in prepare_value().
To resolve this, you can try to cache the value and result. If value hasn't changed, return cached result. Something like:
class CustomRelatedField(forms.Field):
def __init__(self, model, limit=None, multiple=False, create_objects=True, *args, *kwargs):
self.cached_result = None
self.cached_value = None
...
def prepare_value(self, value):
#check we have cached result
if value == self.cached_value:
return self.cached_result
if self.multiple:
result = [obj.name for obj in self.model.filter(pk__in=value)]
else:
result = self.model.object.get(pk=value)
#cache the result and value
self.cached_result = result
self.cached_value = value
return result
Not sure how good/bad this work around though!

Related

How to prevent duplicates when using ModelChoiceFilter in Django Filter and Wagtail

I am trying to use filters with a Wagtail Page model and a Orderable model. But I get duplicates in my filter now. How can I solve something like this?
My code:
class FieldPosition(Orderable):
page = ParentalKey('PlayerDetailPage', on_delete=models.CASCADE, related_name='field_position_relationship')
field_position = models.CharField(max_length=3, choices=FIELD_POSITION_CHOICES, null=True)
panels = [
FieldPanel('field_position')
]
def __str__(self):
return self.get_field_position_display()
class PlayerDetailPage(Page):
content_panels = Page.content_panels + [
InlinePanel('field_position_relationship', label="Field position", max_num=3),
]
class PlayerDetailPageFilter(FilterSet):
field_position_relationship = filters.ModelChoiceFilter(queryset=FieldPosition.objects.all())
class Meta:
model = PlayerDetailPage
fields = []
So what I am trying to do is create a filter which uses the entries from FIELD_POSITION_CHOICES to filter out any page that has this position declared in the inline panel in Wagtail.
As you can see in the picture down below, the filters are coming through and the page is being rendered. (These are 2 pages with a list of 3 field positions).
So Page 1 and Page 2 both have a "Left Winger" entry, so this is double in the dropdown. The filtering works perfectly fine.
What can I do to prevent this?
The solution should be something like this (Credits to Harris for this):
I basically have one FieldPosition object per page-field position, so it's listing all of the objects correctly. I suspect I should not use the model chooser there, but a list of the hard coded values in FIELD_POSITION_CHOICES and then a filter to execute a query that looks something like PlayerDetailPage.objects.filter(field_position_relationship__field_position=str_field_position_choice). But what is the Django Filter way of doing this?
Raf
In my limited simplistic view it looks like
class PlayerDetailPageFilter(FilterSet):
field_position_relationship = filters.ModelChoiceFilter(queryset=FieldPosition.objects.all())
is going to return all the objects from FieldPosition and if you have 2 entries for 'left wing' in here (one for page 1 and one for page 2) then it would make sense that this is duplicating in your list. So have you tried to filter this list queryset with a .distinct? Perhaps something like
class PlayerDetailPageFilter(FilterSet):
field_position_relationship = filters.ModelChoiceFilter(queryset=FieldPosition.objects.values('field_position').distinct())
I found the solution after some trial and error:
The filter:
class PlayerDetailPageFilter(FilterSet):
field_position_relationship__field_position = filters.ChoiceFilter(choices=FIELD_POSITION_CHOICES)
class Meta:
model = PlayerDetailPage
fields = []
And then the view just like this:
context['filter_page'] = PlayerDetailPageFilter(request.GET, queryset=PlayerDetailPage.objects.all()
By accessing the field_position through the related name of the ParentalKey field_position_relationship with __.
Then using the Django Filter ChoiceFilter I get all the hard-coded entries now from the choice list and compare them against the entries inside the PlayerDetailPage query set.
In the template I can get the list using the Django Filter method and then just looping through the query set:
<form action="" method="get">
{{ filter_page.form.as_p }}
<input type="submit" />
</form>
{% for obj in filter_page.qs %}
{{ obj }} >
{% endfor %}

Testing for presence of form instance in Jinja2

I'm using WTforms with Jinja2 and want to change my templates page title depending on whether I am creating a new instance of editing an existing form object.
This is what I wrote in the template:
{% block title %}{% if form.obj %}Edit{% else %}New{% endif %} Post{% endblock %}
What I expect to see:
if the form is filled out I expect to see "Edit Post" in the page title.
if the form is empty I expect to see "New Post" in the page title.
What I get: "New Post" in both instances.
Here is my PostHandler that is passing the form values.
def with_post(fun):
def decorate(self, post_id=None):
post = None
if post_id:
post = models.BlogPost.get_by_id(int(post_id))
if not post:
self.error(404)
return
fun(self, post)
return decorate
class PostHandler(BaseHandler):
def render_form(self, form):
self.render_to_response("edit.html", {'form': form})
#with_post
def get(self, post):
self.render_form(MyForm(obj=post))
#with_post
def post(self, post):
form = MyForm(formdata=self.request.POST, obj=post)
if post and form.validate():
form.populate_obj(post)
post.put()
post.publish()
self.render_to_response("published.html", {'post': post})
elif self.request.POST and form.validate():
post = models.BlogPost()
post.title = form.title.data
post.body = form.body.data
post.tags = form.tags.data
post.publish()
self.render_to_response("published.html", {'post': post})
else:
self.render_to_response('edit.html', {'form':form})
In short, all I'm trying to do is test whether the form is filled, and change my page title "New Post" or "Edit Post" accordingly.
While Form accepts a obj argument, it doesn't actually store it, it just uses that obj to fill in any blanks that formdata didn't provide. So when you ask Jinja2 {% if form.obj %} it's always going to be False, because there is never a obj property (unless you have a field that happens to be called obj of course).
If you're editing a post, you'll have an id to work with so you know which post to update in the database, so where are you currently storing that? Assuming you store it as a hidden field, you could just do:
{% if form.id.data == None %}Must be a New form {% endif %}
If you wanted to check if the entire form was empty, you could access the form.data dictionary, and make sure all the entries are None, although you need to be careful, because I know that FileField returns a u'None' instead of a real None, so you'd have to double check what Fields you care about.

What can cause this difference between prod and dev?

Same code, in production don't work while in development yes.
models:
class Comput(ndb.Model):
#staticmethod
def membership(user):
q = ndb.gql("SELECT * FROM Members WHERE member = :1", user)
result = q.fetch()
return [m.comput.get() for m in result]
#return [m.comput.get() for m in q] #same issue (dev yes, prod no)
class Members(ndb.Model):
comput = ndb.KeyProperty(kind=Comput)
member = ndb.UserProperty()
handler:
comput_list = Comput.membership(users.get_current_user())
template:
{% for comput in comput_list %}
<tr onclick="location.href='/comput?id={$ comput.key.id() $}'">
Traceback (only in production):
UndefinedError: 'None' has no attribute 'key'
This appears even when comput_list have entities.
update: No autogenerate index for Members, I tried to add manually but nothing changes
Looks like one of your Members objects has None for its comput property. I would use the production data viewer (or add a log statements) to find which member it is.

database design for user/reviewer in django database model

i am kinda new to database design, but i would like to create three tables "User" and "Review" and "Topic" for a database in django.
I will try to explain it in detail here:
For example, I have User, Topic and Review models in models.py. one user can only write one review for one topic from other users.
let's say: Mike, John, Peter are the three Users.
Mike posted "Hello World" topic. John can only write one review for the topic "Hello World", Peter can also write one review for the same. John and Peter can not post another review for the same topic(they can only modify it). If Mike post another topic, John and Peter can post another review for the new topic. the same rule apply to other users.
please if you could, could you please provide some sample code for this issue? thanks a lot.
If you are trying to figure out how to set up your models.py, visit the django documentation, and look at Writing your first app (https://docs.djangoproject.com/en/dev/intro/tutorial01/). It goes from start to finish writing your first application and you will learn how the system works.
If you wanted more specifics for the paradigm of your case, here's what I would do. I would probably handle this in the view/template and submit/edit the review with Dajaxice calls to the database. If a review by the current user exists, it will show the data, if it doesn't it will be a blank entry that will use Dajax to submit the content. In the python method that the Dajax calls, you would try to find a review, and if one exists while attempting to add a new one, something went wrong and you can handle the error, otherwise it is saved for all to see.
For example, in models.py:
class User(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Review(models.Model):
title = models.CharField(max_length=64)
message = models.TextField()
topic = models.ForeignKey(Topic)
user = models.ForeignKey(User)
def __unicode__(self):
return self.title
class Topic
title = models.CharField(max_length=64)
message = models.TextField()
user = models.ForeignKey()
def __unicode__(self):
return self.title
in views.py:
class Post(models.Model): # This is defined a model, but not part of the data layer, it really is view code.
topic = None
your_review = None
other_reviews = None
def __unicode__(self):
return ''
def GetDetails(request):
posts = () # to be returned to and looped by the Template.
topics = Topic.objects.all().order_by('-posting_date') # posting_date descending.
for t in topics:
post = Post()
post.topic = t
post.your_review = Review.objects.filter(topic__id=t.id, user__id=<current_user_id>)
post.other_reviews = Review.objects.filter(topic__id=t.id, ~Q(user__id=<current_user_id>)
# Append to the posts array.
posts.append(post)
return render_to_response('index.htm', {'posts': posts}, context_instance=RequestContext(request))
in your index.htm:
{% if posts %}
{% for p in posts %}
<div>
<div class="title">{{ p.topic.title }}</div>
<div class="message">{{ p.topic.message }}</div>
<div class="other_reviews">
{% if p.other_reviews %}
{% for r in p.other_reviews %}
<div class="review_title">{{ r.title }}</div>
<div class="review_message">{{ r.message }}</div>
{% endfor %}
{% endif %}
<div>
<input type="text" value="{% if p.your_review %}{{ p.your_review.title }}{% endif %}">
</div>
<div>
<textarea>{% if p.your_review %}{{ p.your_review.message }}{% endif %}</textarea>
</div>
</div>
</div>
{% endfor %}
{% endif %}

Django ForeignKey on a View

i'm building a django app from an existing database. for better or worse, i have a couple of views that i would like to base my models off. they look something like this:
class Device(Model):
id = models.IntegerField( primary_key=True, db_column='node_id' )
name = models.CharField(max_length=127, db_column='node' )
class Meta:
db_table = 'node' # db view
managed = False
class Entity(Model):
id = models.IntegerField( primary_key=True, db_column='_id' )
device = models.ForeignKey(Device, db_column='node_id' )
class Meta:
db_table = 'entity' # db view
managed = Fase
so things work okay. however, when i try to use a template that utilises the ForeignKey it is very slow:
{% for e in entities %}
{{ e.device.name }}
{% endfor %}
looking at the logs, it appears to be repeating queries for each 'node_id', and ultimately timeout out.
(of course, if i do not include e.device.name is all quick)
is there a way i can optimise this?
to be fair, the 'entity' view already has the name of the device as another field (node), so i could use this instead but i would like the relation to exist.
have you try .select_related() in your view?
entities = Entity.objects.select_related('device').filter(...)
in the other hand, if it is and old database, and no autogenerated by django, probably node_id may not be an index in the database, this will slow down a lot any JOIN.

Resources