Consider a Django model that forms a connection between two other model instances:
class User(models.Model):
pass
class Meeting(models.Model):
user_a = models.ForeignKey(User, related_name='a_meetings')
user_b = models.ForeignKey(User, related_name='b_meetings')
Now suppose you want to define a function that returns all of a Users meetings, regardless of if he is side a or b. The naive implementation would be:
class User(models.Model):
#property
def meetings(self):
return list(self.a_meetings.all()) + list(self.b_meetings.all())
What's the 'right' way to have this function return the matching QuerySet instead of a list? Bonus points for an elegant solution that doesn't use an ugly Q query ;)
I think Qs are inevitable here.
The main principle with these things is always to start from the model you actually want to get: so since you want meetings, start there.
return Meeting.objects.filter(Q(user_a=self) | Q(user_b=self))
Related
I'm working on django models. At the design level I'm confused if I can implement functions inside model class. If I can implement then what kind of functions should be going inside and what kind of functions shouldn't. I couldn't find a document regarding this apart from doc
Or is there any document where I can figure out about this?
Yes, of course you can create functions inside the model class. It's highly recommended especially for things that you have to calculate specifically for objects of that model.
In example it's better to have function that calculates let's say Reservation time. You don't have to put that info inside database, just calculate only when it's needed:
class Reservation(models.Model):
valid_to = models.DateTimeField(...)
def is_valid(self):
return timezone.now() < self.valid_to
Depending on what you actually need/prefer it might be with #property decorator.
I guess you are asking about the old discussion "Where does the business logic go in a django project? To the views, or the model?"
I prefer to write the business logic inside of the views. But if it happens that I need a special "treatment" of a model several times in multiple views, I turn the treatment inside of the model.
To give you an example:
# models.py
class Customer(models.Model):
name = models.CharField(max_length=50, verbose_name='Name')
# views.py
def index(request):
customer = Customer.objects.all().first()
name = str.upper(customer.name) # if you need that logic once or twice, put it inside of the view
return HttpResponse(f"{name} is best customer.")
If you need the logic in multiple views, over and over again, put it inside of your model
# models.py
class Customer(models.Model):
name = models.CharField(max_length=50, verbose_name='Name')
#property
def shouted_name(self):
return str.upper(self.name)
# views.py
def index(request):
customer = Customer.objects.all().first() # grab a customer
return HttpResponse(f"{customer.shouted_name} is best customer.")
def some_other_view(request):
customer = Customer.objects.all().first() # grab a customer
customer_other = Customer.objects.all().last() # grab other customer
return HttpResponse(f"{customer.shouted_name} yells at {customer_other}")
# see method shouted_name executed in two views independently
I have a question whether or not it is possible to use the generic UpdateView class to edit "both sides" of a many-to-many relationship.
I have the following classes defined in models.py:
class SomeCategory(models.Model):
code = models.CharField(max_length=5)
name = models.CharField(max_length=40)
class SomeClass(models.Model):
code = models.CharField(max_length=3, unique=True)
name = models.CharField(max_length=30, unique=False)
age = models.IntegerField(null=False)
allowed_categories = models.ManyToManyField(SomeCategory)
These are both dictionary type tables that store sets of configuration data for my application. To allow editing the dictionaries I use simple UpdateViews:
class SomeClassUpdate(UpdateView):
model = SomeClass
template_name = 'admin/edit_class.html'
fields = ['code', 'name', 'age', 'allowed_categories']
ordering = ['code']
This works fine, I get a nice multi-select and everything is perfect. However, I would like to have the possibility to edit the relationship from the side of the SomeCategory table, so I can choose which SomeClass elements are linked to a certain SomeCategory:
class SomeCategoryUpdate(UpdateView):
model = SomeCategory
template_name = 'admin/edit_category.html'
fields = ['code', 'name', ??????? ]
ordering = ['code']
I have tried adding the related_name attribute to the SomeCategory model, but that did not work.
Any ideas if this can be done without using a custom ModelForm?
Key library versions:
Django==1.11.8
psycopg2==2.7.4
PS: this is my very first question asked on stackoverflow, so please let me know if my post is missing any mandatory elements.
Your issue is in the models.py file. You have two classes, but only one of them mentions the other one. You would think that this should be enough since you are using ManyToManyField after all and assume that it would automatically create every connection leading both ways... Unfortunately this is not true. On the database level it does indeed create a separate intermediary table with references to objects in both original tables, but that doesn't mean that both of them will be automatically visible in Django Admin or similar.
If you would attempt to simply create another someclass = models.ManyToManyField(SomeClass) in the SomeCategory class that would fail. Django would try to create another separate intermediary table through which the connection between two main tables is established. But because the name of the intermediary table depends on where you define the ManyToManyField connection, the second table would be created with a different name and everything would just logically collapse (two tables having two separate default ways to have a ManyToMany connection makes no sense).
The solution is to add a ManyToManyField connection to SomeCategory while also referencing that intermediary/through table that was originally created in the SomeClass class.
A couple of notes about Django/python/naming/programming conventions:
Use the name of the table you are referencing to, as the name of the field that is containing the info about that connection. Meaning that SomeClass's field with a link to SomeCategory should be named somecategory instead of allowed_categories.
If the connection is one-to-many - use singular form; if the connection is many-to-many - use plural. Meaning that in this case we should use plural and use somecategories instead of somecategory.
Django can automatically pluralize names, but it does it badly - it simply adds s letter to the end. Mouse -> Mouses, Category -> Categorys. In those kind of cases you have to help it by defining the verbose_name_plural in the special Meta class.
Using references to other classes without extra 's works only if the the class was already defined previously in the code. In the case of two classes referring to each other that is true only one way. The solution is to put the name of the referred class in the quotation marks like 'SomeCategory' instead of SomeCategory. This sort of reference, called a lazy relationship, can be useful when resolving circular import dependencies between two applications. And since by default it's better to keep the style the same and to avoid unnecessary brain energy wasting of "I will decide whether or not to use quotation marks depending on the order the classes have been organized; I will have to redo this quotation marks thingie every time I decide to move some code pieces around" I recommend that you simply use quotation marks every time. Just like when learning to drive a car - it's better to learn to always use turn signals instead of first looking around and making a separate decision of whether someone would benefit from that information.
"Stringifying" (lazy loading) model/class/table name is easy - just add 's around. You would think that stringifying the "through" table reference would work the same easy way. And you would be wrong - it will give you the ValueError: Invalid model reference. String model references must be of the form 'app_label.ModelName'. error. In order to reference the stringified "through" table you need to: (a) add 's around; (b) replace all dots (.) with underscores (_); (c) delete the reference to through!.. So SomeClass.somecategories.through becomes 'SomeClass_somecategories'.
Therefore the solution is this:
class SomeCategory(models.Model):
code = models.CharField(max_length=5)
name = models.CharField(max_length=40)
someclasses = models.ManyToManyField('SomeClass', through='SomeClass_somecategories', blank=True)
class Meta:
verbose_name_plural = 'SomeCategories'
class SomeClass(models.Model):
code = models.CharField(max_length=3, unique=True)
name = models.CharField(max_length=30, unique=False)
age = models.IntegerField(null=False)
somecategories = models.ManyToManyField('SomeCategory')
After this it should be obvious what kind of final changes to make to your UpdateView classes.
You can achieve this in the view and form, without having to specify the additional ManytoMany connections in the
models, using something like the following:
In the View
class SomeClassUpdate(UpdateView):
model = SomeClass
form_class = SomeClassUpdateForm # to specify the form
template_name = 'admin/edit_class.html'
def form_valid(self, form, *args, **kwargs):
initial_somecategorys = SomeCategory.objects.filter(allowed_categories__pk=form.instance.pk)
amended_somecategorys = form.cleaned_data['allowed_categroies']
remove = [x for x in initial_somecategorys if x not in amended_somecategorys]
add = [x for x in amended_somecategorys if x not in initial_somecategorys]
for somecategory in add:
somecategory.allowed_categories.add(form.instance)
somecategory.save()
for somecategory in remove:
somecategory.allowed_categories.remove(form.instance)
somecategory.save()
return super().form_valid(form)
In the Form
The init method at the top pre-populates the form with entries saved on the model.
class SomeClassUpdateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SomeClassUpdateForm, self).__init__(*args, **kwargs)
try:
obj = kwargs['instance']
self.fields["some_categories"].initial = SomeCategory.objects.filter(allowed_categories__pk=form.instance.pk)
except (AttributeError, KeyError): # to catch NoneType if new entry being created.
pass
some_categories = forms.ModelMultipleChoiceField(
required=False,
queryset=SomeCategory.objects.all(),
)
class Meta:
model = SomeClass
fields = [
'some_categories'
..etc
]
This should work. I've writen similar code in one of my projects, and it's working fine. However, I don't know if it's
structurally best to use methods like this and not alter the model relationships or whether it's preferable to
alter the model relationships as outlined in other replies. So I'd be interested to know other peoples views on what
the best approach is.
user_id user_follow
3 2
3 2
3 2
2 3
login user id=3, now i want to get people who follow me and i follow them, mutual follow in django framework for login user. above senario show login user(id=3) follow the user(id=2)
and user(id=2) also follow the login user(id=3) now I want to show to login user, how many user follow you and you follow him. using django orm
class Cause(models.Model):
description = models.TextField()
start_date = models.DateTimeField(null=True, blank=True)
creation_date = models.DateTimeField(default=datetime.now)
creator = models.ForeignKey(User, related_name='cause_creator_set')
attendees = models.ManyToManyField(User)
class Attendance(models.Model):
user = models.ForeignKey(User)
cause = models.ForeignKey(Cause)
user_follow = models.IntegerField(max_length=255)
registration_date = models.DateTimeField(default=datetime.now)
follow = models.BooleanField(default=False)
def __unicode__(self):
return "%s is attending %s" % (self.user.username, self.event)
class Meta(object):
verbose_name_plural = "Attendance"
There are multiple issues here, I'm going to try and address them all.
The relationship your models describe is what called a Extra field on ManyToMany relationship. Simply put, Cause has a ManyToMany to User, with Attendance being an intermediatry table. The benefits of using the through instruction for this are mostly for ease-of-access, and you can read the documentation I linked for examples. So:
attendees = models.ManyToManyField(User, through='Attendance')
Now let's talk about Attendance. I have a problem with two fields:
user = models.ForeignKey(User)
user_follow = models.IntegerField(max_length=255)
follow = models.BooleanField(default=False)
Yes, I showed 3 because only 2 of them are problematic. So you have a ForeignKey to User, right? But what is a ForeignKey? It's simply an IntegerField that points on the row (pk) on a different table. There are other stuff that happens here, but the basic thing is it's just an integerfield. And then you wanted to link that model to user again, right? So instead of adding another ForeignKey, you used an IntegerField.
Do you understand why that's a bad thing? I'll explain - you can't use the ORM on your own logic. Now the basic thing I'm assuming you are trying to accomplish here is actually this:
# let's pretend that User isn't built-in
class User(models.Model):
follow = models.ManyToManyField('self')
Then there's the follow Boolean which I really don't get - if a user is linked to another one, doesn't that automatically mean he's following him? seems more like a method than anything.
In anyway, I can think of two very valid options:
Use another ForeignKey. Really, if that what makes sense, there's nothing wrong with multiple ForeignKeys. In fact, it will create a sort of ManyToMany from user to himself going through Attendance as an intermediary table. I think this makes sense
Create your own User class. Not a terribly complicate feat. I also recommend this, since it seems you have plenty of customization. The built in User is great because it comes pretty much out of the box. But at the same time, the built-in user is pretty much built around the admin interface and kinda limits you a lot. A custom one would be easier. Then you can do the recursive relationship that seems to be what you're looking for, then implament is_following as a boolean method (seems more appropriate).
The last thing I'm gonna do is show you the method you're looking for. I'm showing you how to do it though as I mentioned, I strongly recommend you first take my suggestion and build your own User model. The method would look different of course, but it wouldn't be hard to convert.
That being said, the key here is simply creating two methods - one for checking if a user is related. A second one for checking if you're being followed by him. So if I understand you correctly, you're trying to achieve:
#using your way
def follows(a, b):
if Attendence.objects.filter(user__pk=a, user_follow=b).count() > 0:
return True
return False
def mutual(a, b):
if follows(a, b) and follows(b, a):
return True
return False
a, b = 2, 3
mutual(2, 3) # will output True
#using a custom User Class:
class MyUser(AbstractBaseUser):
# required fields here...
follow = models.ManyToManyField('self')
def is_following(self, user):
if user in self.follow.all():
return True
return False
def mutual(self, user):
if self.is_following(user) and user.is_following(self):
return True
return False
I'm new to Django, moved from PHP with Propel ORM engine.
So here is what I am currently doing in Django. My website has several models like
Book,
Publisher,
Comment,
Article and so on (it's not the point)
Each of them can can be
liked or disliked by a user (only once) changing the model's rating by +1 or -1.
In terms of PHP i would create a behavior, for ex. "Rateable" which would add some fields and methods to the original model and query class (like get_rating(), order_by_rating(), etc) and create a separate table for each model, for ex. book_rating_history which would hold all of the ratings for each object, to determine if the user can or can't change the rating (or show all object's ratings, if necessary). So all I would need to do is to specify the "Rateable" behavior in the model declaration, and that's all. Everything else is done automatically.
The question is - how to solve this in Django? How to model correctly? Which techniques do you use in similar cases?
You'll want to store ratings and books separately, something like this (untested).
from django.contrib.auth.models import User
from django.db import models
from django.db.models import Sum
class BookRating(models.Model):
user = models.ForeignKey(User)
book = models.ForeignKey('Book')
# you'll want to enforce that this is only ever +1 or -1
rating = models.IntegerField()
class Meta:
unique_together = (('user', 'book'),)
class Book(models.Model):
title = models.CharField(max_length = 50)
def rating(self):
return BookRating.objects.filter(book = self).aggregate(Sum('rating'))
unique_together enforces that each user can only rate a given book once.
You can then use this something like:
book = Book.objects.get(pk = 1)
rating = book.rating()
Add a comment if you have problems with this - I've not tested it, but hopefully this is enough to get you started.
You can probably avoid each object (books, publishers, comments, articles) having a separate rating object using content types, if you want.
Alternatively, you might consider looking at existing reusable apps that handle liking, like django-likes or phileo.
You can define special methods (for example vote, get_rating, etc.) only onсe in abstract model and then create your "Rateable" models using this one.
class Rateable(models.Model):
class Meta:
abstract = True
def vote(self, *args, **kwargs):
...
def rating(self, *args, **kwargs):
...
class Book(Rateable):
...
Also it is better to use single model for storing rating data witch content types as noticed Dominic Rodger
I'm making a little vocabulary-quiz app, and the basic model for a word is this:
class Word(models.Model):
id = models.AutoField(primary_key=True)
word = models.CharField(max_length=80)
id_image = models.ForeignKey(Image)
def __unicode__(self):
return self.word
class Meta:
db_table = u'word'
The model for words I'm currently quizzing myself on is this:
class WordToWorkOn(models.Model):
id = models.AutoField(primary_key=True)
id_student = models.ForeignKey(Student)
id_word = models.ForeignKey(Word)
level = models.IntegerField()
def __unicode__(self):
return u'%s %s' % (self.id_word.__unicode__(), self.id_student.__unicode__() )
class Meta:
db_table = u'word_to_work_on'
Where "level" indicates how well I've learned it. The set of words I've already learned has this model:
class WordLearned(models.Model):
id = models.AutoField(primary_key=True)
id_word = models.ForeignKey(Word, related_name='word_to_learn')
id_student = models.ForeignKey(Student, related_name='student_learning_word')
def __unicode__(self):
return u'%s %s' % (self.id_word.__unicode__(), self.id_student.__unicode__() )
class Meta:
db_table = u'word_learned'
When a queryset on WordToWorkOn comes back with too few results (because they have been learned well enough to get moved into WordLearned and deleted from WordToWorkOn), I want to find a Word to add to it. The part I don't know a good way to do is to limit it to Words which are not already in WordLearned.
So, generally speaking, I think I want to do an .exclude() of some sort on a queryset of Words, but it needs to exclude based on membership in the WordLearned table. Is there a good way to do this? I find lots of references to joining querysets, but couldn't find a good one on how to do this (probably just don't know the right term to search for).
I don't want to just use a flag on each Word to indicate learned, working on it, or not learned, because eventually this will be a multi-user app and I wouldn't want to have flags for every user. Hence, I thought multiple tables for each set would be better.
All advice is appreciated.
Firstly, a couple of notes about style.
There's no need to prefix the foreign key fields with id_. The underlying database field that Django creates for those FKs are suffixed with _id anyway, so you'll get something like id_word_id in the db. It'll make your code much clearer if you just call the fields 'word', 'student', etc.
Also, there's no need to specify the id autofields in each model. They are created automatically, and you should only specify them if you need to call them something else. Similarly, no need to specify db_table in your Meta, as this is also done automatically.
Finally, no need to call __unicode__ on the fields in your unicode method. The string interpolation will do that automatically, and again leaving it out will make your code much easier to read. (If you really want to do it explicitly, at least use the unicode(self.word) form.)
Anyway, on to your actual question. You can't 'join' querysets as such - the normal way to do a cross-model query is to have a foreignkey from one model to the other. You could do this:
words_to_work_on = Word.objects.exclude(WordLearned.objects.filter(student=user))
which under the hood will do a subquery to get all the WordLearned objects for the current user and exclude them from the list of words returned.
However, and especially bearing in mind your future requirement for a multiuser app, I think you should restructure your tables. What you want is a ManyToMany relationship between Word and Student, with an intermediary table capturing the status of a Word for a particular Student. That way you can get rid of the WordToWorkOn and WordLearned tables, which are basically duplicates.
Something like:
class Word(models.Model):
word = models.CharField(max_length=80)
image = models.ForeignKey(Image)
def __unicode__(self):
return self.word
class Student(models.Model):
... name, etc ...
words = models.ManyToManyField(Word, through='StudentWord')
class StudentWord(models.Model):
word = models.ForeignKey(Word)
student = models.ForeignKey(Student)
level = models.IntegerField()
learned = models.BooleanField()
Now you can get all the words to learn for a particular student:
words_to_learn = Word.objects.filter(studentword__student=student, studentword__learned=False)