Change other objects inside model's custom save method in django - django

Well, I have two django models for storing Questions and Questionnaires. Every question is inside a questionnaire. Every Question should have a field to store the order it is going to appear in a questionnaire. My questions is what is about the best way to handle changes in the order of a question.
The code have for the models:
class Questionnaire(models.Model):
name = models.CharField(max_length = 100)
time_creation = models.DateTimeField(auto_now=True)
class Question(models.Model):
questionnaire = models.ForeignKey(Questionnaire, related_name='questions', on_delete=models.CASCADE)
text = models.TextField()
order = models.IntegerField()
One way I thought of doing it was by overriding the save method for the Question model, like that:
def save(self, *args, **kwargs):
all_questions = self.questionnaire.questions.all()
if (self.order == None or self.order > len(all_questions)+1 or self.order == 0):
self.order = len(all_questions)+1 # starts in 1
else:
previous = all_questions.filter(order=self.order)
if len(previous)>0:
p = previous[0]
p.order = self.order+1
p.save()
super().save(*args, **kwargs)
It is kind of a recursive function, inside save() i verify if there is a Question with the same order number and increase it then call the save (inside the save), which will do the same for this object.
Is it a good way to solve the problem? Django has a few idiosyncrasies that i might not be aware of and might bite me latter. Should i use signals, maybe? Do it post save? Thank you for your help!

Django provides a very simple mechanism through model meta options, namely order_with_respect_to.
I believe using it would already make things easier:
class Question(models.Model):
questionnaire = models.ForeignKey(Questionnaire)
# ...
class Meta:
order_with_respect_to = 'questionnaire'
This adds two methods to the Questionnaire model. Both let you handle the order of the Question objects using a list of Question primary keys.
Get the order
questionnaire = Questionnaire.objects.get(id=1)
questionnaire.get_question_order()
[1, 2, 3]
Set the order
questionnaire.set_question_order([3, 1, 2])
Please note that this operation will add an _order column to the database and implicitly set ordering to use this.
You can now implement all further business logic by manipulating the order list. Just an example helper for finding the order of a Question object:
questionnaire = Questionnaire.objects.get(id=1)
question_order = questionnaire.get_question_order()
question = Question.objects.get(foo='bar')
position = question_order.index(question.pk)
I am not sure if you should really go down that road (using an order field), but if you do, at least use a PositiveIntegerField and constrain it to be unique. I don't know where your inputs comes from but you should always try to sanitise them to a sequence, throw them in a list, and then set the order of the Question objects all at once.

Related

How to validate whether a ManyToMany relationship exists Django?

class Interest(models.Model):
name = models.CharField(max_length=255)
class Person(models.Model):
name = models.CharField(max_length=255)
interests = models.ManyToManyField(Interest, related_name='person_interests')
def add_interest(self, interest_pk):
interest = Interest.objects.get(pk=interest_pk)
if not interests in self.interests:
self.interests.add(interest)
The above code does not work, but it indicates what I want to do.
In short, I want to validate whether a relation exists or not, if it does not, then I add the relationship. What is the efficient way to do this using django models.
Thanks.
If you look at the _add_items function of ManyRelatedManager, it already takes care of what you want here:
vals = (self.through._default_manager.using(db)
.values_list(target_field_name, flat=True)
.filter(**{
source_field_name: self.related_val[0],
'%s__in' % target_field_name: new_ids,
}))
new_ids = new_ids - set(vals)
It removes all the ids which are already present in the through table. So you don't really need to check anything. You can directly use add function:
def add_interest(self, interest_pk):
self.interests.add(interest_pk)
And, of course it will throw error if the interest_pk doesn't exist yet because that's the basic requirement of add function.
try this
def add_interest(self, interest_pk):
interest = Interest.objects.get(pk=interest_pk)
if interest not in self.interests.all():
self.interests.add(interest)
Please check if this helps.
person = Person.objects.filter(name = 'something):
if person.count() > 0:
interests = person[0].interests.all()
if interests.count() > 0:
###DO something..
In this case,
def add_interest(self, interest_pk):
interests = self.interests.all()
if interests.count():
### Add

Django - sorting object based on user defined order in template

I want the user to be able to order a list of objects in a table using javascript. Then, in a django function I would like to sort those object based on the same ordering, not on an attribute.
Is it possible? I was thinking about passing a list of pk from the template to the view and then ordering the objects according to this list, but I have not found a way to do it yet.
I don't think this is possible with queryset. Try following:
pk_list = [2, 1, 3, 4]
pk2obj = {obj.pk: obj for obj in Model.objects.all()}
objects_ordered = [pk2obj[pk] for pk in pk_list]
pkg2obj is mapping between pk and model instance object. To make a dictionary I used dictionary comprehension.
If you want to omit deleted objects:
objects_ordered = [pk2obj[pk] for pk in pk_list if pk in pk2obj]
Else if you want to replace deleted objects with default value (None in following code):
objects_ordered = [pk2obj.get(pk, None) for pk in pk_list]
I've had to solve this exact problem before.
If you want the user to be able to reorder them into a user-defined order, you can easily define a field to store this order.
As you say, initially, you could serve them in order according to id or an upload_date DateTimeField. But you could also have an PositiveIntegerField in the model, named position or order, to represent the user-defined order.
class MediaItem(models.Model):
user = models.ForeignKey(User)
upload_date = models.DateTimeField(auto_now_add = True)
position = models.PositiveIntegerField()
Whenever a user changes the order on the frontend, the JS can send the new order as an array of objects (ie. new_order = [{"pk":3, "position":1}, {"pk":1, "position":2}, {"pk":2, "position":3}]). The view can look up each instance by pk, and change the position:
for obj in new_order:
media_item = MediaItem.objects.get(pk=obj['pk'])
media_item.position = obj['position']
media_item.save()
Then always query using
objects_ordered.objects.order_by('position')
That's how we managed to do it. If you have more specific questions regarding this approach, feel free to ask in the comments.
Edit:
If the same object can be a member of many different groups or lists, and you want to store the position of the membership within that list, you can achieve this using a through model. A through model is useful when you need to store data that relates to the relationship between two objects that are related. In addition to the MediaItem class shown above, this is what your other models would look like:
class Album(models.Model):
media_items = models.ManyToManyField(MediaItem,
related_name = 'album_media_items',
through = 'Membership')
class Membership(models.Model):
album = models.ForeignKey(Album,
related_name = 'album')
media_item = models.ForeignKey(MediaItem,
related_name = 'media_item')
date = models.DateTimeField(auto_now_add = True)
position = models.PositiveIntegerField()
Then, you could query the Membership instances, instead of the MediaItem instances.
# get id of list, or album...
alb = Album.objects.get(pk=id_of_album)
media_items = Membership.objects.filter(album=alb).order_by('position')
for item in media_items:
# return the items, or do whatever...
# keep in mind they are now in the user-defined order
You can do this:
pk_list = [1,5,3,9]
foo = Foo.objects.filter(id__in=pk_list)
#Order your QuerySet in base of your pk_list using Lambda
order_foo = sorted(foo, key = lambda:x , pk_list.index(x.pk))

django: Models with multiple Foreign Key queries

I have finished a proof-of-concept django project and now want to redesign the models to be more robust.
The basic model is called a PhraseRequest:
class PhraseRequest(models.Model):
user = models.ForeignKey(User)
timestamp = models.DateTimeField()
phrase = models.TextField()
Now the complication comes in that one PhraseRequest has a bunch of associated models, PhraseRequestVote, Phrase (a response), PhraseRequestComment&c.
Now when I list say, the top ten Phrase Requests in order of votes, my template has a for-each loop which is fed the ten PhraseRequest objects. It then populates the HTML with the request, and all it's associated data.
So far I have been adding to each PhraseRequest's dictionary to achieve this:
for r in phrase_requests:
r.votes = PhraseRequestVote.objects.filter(request=r)
r.n_votes = sum([v.weight for v in r.votes])
r.comments = PhraseRequestComment.objects.filter(request=r)
#and so on
Intuitively, this doesn't seem right - There must be a "correct" way to do this. Do I need to redesign the models? The query?
You can make function in your model and order it in your view. Like this
models.py
class model_name(models.Model)
........
def votes(self):
return Vote_Name.objects.filter(phrase_id=self).count()

Django categories, sub-categories and sub-sub-categories

I have a simple category model:
class Category(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField()
parent = models.ForeignKey('self', blank = True, null = True, related_name="children")
At first, my data seemed to need only categories and subcategories, but I realized that there are some cases where I still want to sub-sub-categorize.
I want my urls to be category/subcategory/sub-subcategory
I was thinking about how to implement this but I am not sure, since my url pattern matching looks like this:
url(r'^business/(?P<parent>[-\w]+)/(?P<category_name>[-\w]+)$', 'directory.views.show_category'),
Basically only allowing a single sub-category, since my view method accepts these two parameters.
What is the best way to handle this?
What about unlimited levels? At urls.py:
url(r'^business/(?P<hierarchy>.+)/', 'directory.views.show_category')
And at directory/views.py:
def show_category(request, hierarchy):
category_slugs = hierarchy.split('/')
categories = []
for slug in category_slugs:
if not categories:
parent = None
else:
parent = categories[-1]
category = get_object_or_404(Category, slug=slug, parent=parent)
categories.append(category)
...
Don't forget to add unique_together = ('slug', 'parent',) to Category.Meta, otherwise you are doomed.
[update]
could I just query the db with category_slugs[-1] and if the obtained category has no children, we know its a leaf category, otherwise, we know it has subcategories and we show them? – alexBrand
#alexBrand: consider the following hypothetical URLs:
/business/manufacture/frozen/pizza/
/business/restaurant/italian/pizza/
/business/delivery-only/italian/pizza/
/business/sports/eating-contest/pizza/
If you think such scenario is possible, then IMHO a simpler test (without the whole hierarchy) is not enough.
What are your real concerns regarding the proposed solution? At the end of the loop, the variable category will hold the correct category_slugs[-1], and you will have the whole hierarchy available in categories. Don't worry about performance, my best advice is: don't try to optimize an elegant solution before profiling (you will be surprised).

django order_with_respect_to with related objects

I am working on a django application that contains a bunch of related objects. I have test objects, each with an ordered set of questions (each question has a correct_answer property). And I also have test attempt objects, which are related to test objects by a foreign key and each have their ordered set of question attempts each with a choice property. Essentially, each question attempt corresponds to a question (a test attempt only passes validation of it has the same number of question attempts as the test it is related to has questions), and then I can check the percentage right and wrong by outputting a values_list of correct_answers and choices and comparing the two lists. My model looks something like this:
ANSWERS = ((0,''),(1,'A'),(2,'B'),(3,'C'),(4,'D'),(5,'E'))
class Question(models.Model):
test = models.ForeignKey(Test,related_name='questions')
correct_answer = models.IntegerField(max_length=1,choices=ANSWERS)
def _get_score(self):
answers = self.test.test_attempts.values_list('answers_choice',flat=True)[self.test.get_question_order().index(self.pk)::self.test.questions.count()]
scores = [x==self.correct_answer for x in answers]
length = len(correct)
if length == 0:
return 0
return float(sum(scores))/length
score = property(_get_score)
class Meta:
order_with_respect_to = 'test'
class Test(models.Model):
testID = models.CharField(max_length=50,verbose_name='Test identification information')
def _get_score(self):
scores = [x.score for x in self.test_attempts.all()]
length = len(scores)
if length == 0:
return 0
return float(sum(scores))/length
score = property(_get_score)
class QuestionAttempt(models.Model):
test_attempt = models.ForeignKey(TestAttempt,related_name='answers')
choice = models.IntegerField(max_length=1,choices=ANSWERS)
def isCorrect(self):
return self.choice == Question.objects.get(pk=self.test_attempt.test.get_question_order()[self.test_attempt.get_questionattempt_order().index(self.pk)]).correct_answer
class Meta:
order_with_respect_to = 'test_attempt'
class TestAttempt(models.Model):
test = models.ForeignKey(Test,related_name='test_attempts')
student = models.ForeignKey(UserProfile,related_name='test_attempts')
def _get_score(self):
responses = self.answers.values_list('choice',flat=True)
correctAnswers = self.test.questions.values_list('correct_answer',flat=True)
s = [x==y for x,y in zip(responses,correctAnswers)]
length = len(s)
if length == 0:
return 0
return float(sum(s))/length
score = property(_get_score)
class Meta:
unique_together = ('student','test')
If you take a look at the QuestionAttempt Model and within that, the isCorrect method, you see how my predicament. It seems as if that is the only way to check obtain a per-question granularity to check if a given question attempt is right or wrong. My problem is that this one statement is literally 3 or 4 unique database queries (I can't even tell there are so many). I was thinking of using F statements, but I don't know the default name of the ordering column that django uses when specifying order_with_respect_to. Also, to get the score for a given question, I need to do 3+ db queries as well. Is there a better way to do this? How can I access the db entry for the order. I did some searching and theres a property called _order applied to both the question and the questionattempt model. Can I reliably use this? This would greatly simplify some of the code because I could query the database using a filter on the _order property as well
The problem is with your logic in setting up the models. A QuestionAttempt should be directly related to a Question. Relying on the ordering to be the same is dubious at best, and most likely will fail at some point.