Django - How to build an intermediate m2m model that fits? / best practice - django

First of all I have to admit that I'm quite new to all this coding stuff but as I couldn't find a proper solution doing it myself and learning to code is probably the best way.
Anyway, I'm trying to build an app to show different titleholders, championships and stuff like that. After reading the Django documentation I figured out I have to use intermediate models as a better way. My old models.py looks like this:
class Person(models.Model):
first_name = models.CharField(max_length=64)
last_name = models.CharField(max_length=64)
[...]
class Team(models.Model):
name = models.CharField(max_length=64)
team_member_one = models.ForeignKey(Person)
team_member_two = models.ForeignKey(Person)
class Championship(models.Model):
name = models.CharField(max_length=128)
status = models.BooleanField(default=True)
class Titleholder(models.Model):
championship = models.ForeignKey(Championship)
date_won = models.DateField(null=True,blank=True)
date_lost = models.DateField(null=True,blank=True)
titleholder_one = models.ForeignKey(Person,related_name='titleholder_one',null=True,blank=True)
titleholder_two = models.ForeignKey(Person,related_name='titleholder_two',null=True,blank=True)
Championships can be won by either individuals or teams, depending if it's a singles or team championship, that's why I had to foreign keys in the Titleholder class. Looking at the Django documentation this just seems false. On the other hand, for me as a beginner, the intermediate model in the example just doesn't seem to fit my model in any way.
Long story short: can anyone point me in the right direction on how to build the model the right way? Note: this is merely a question on how to build the models and displaying it in the Django admin, I don't even talk about building the templates as of now.
Help would be greatly appreciated! Thanks in advance guys.

So I will take it up from scratch. You seem to be somewhat new to E-R Database Modelling. If I was trying to do what you do, I would create my models the following way.
Firstly, Team would've been my "corner" model (I use this term to mean models that do not have any FK fields), and then Person model would come into play as follows.
class Team(models.Model):
name = models.CharField(max_length=64)
class Person(models.Model):
first_name = models.CharField(max_length=64)
last_name = models.CharField(max_length=64)
team = models.ForeignKey(to=Team, null=True, blank=True, related_name='members')
This effectively makes the models scalable, and even if you are never going to have more than two people in a team, this is good practice.
Next comes the Championship model. I would connect this model directly with the Person model as a many-to-many relationship with a 'through' model as follows.
class Championship(models.Model):
name = models.CharField(max_length=64)
status = models.BooleanField(default=False) # This is not a great name for a field. I think should be more meaningful.
winners = models.ManyToManyField(to=Person, related_name='championships', through='Title')
class Title(models.Model):
championship = models.ForeignKey(to=Championship, related_name='titles')
winner = models.ForeignKey(to=Person, related_name='titles')
date = models.DateField(null=True, blank=True)
This is just the way I would've done it, based on what I understood. I am sure I did not understand everything that you're trying to do. As my understanding changes, I might modify these models to suit my need.
Another approach that can be taken is by using a GenericForeignKey field to create a field that could be a FK to either the Team model or the Person model. Or another thing that can be changed could be you adding another model to hold details of each time a championship has been held. There are many ways to go about it, and no one correct way.
Let me know if you have any questions, or anything I haven't dealt with. I will try and modify the answer as per the need.

Related

Database normalization in django

I need an optimally normalized database structure to achieve the following requirement.
models.py
class Learning_Institute(models.Model):
name = models.TextField()
user = models.ManyToManyField(settings.AUTH_USER_MODEL)
class Course(models.Model):
title = models.CharField(max_length=50)
instructor = models.ForeignKey(User, on_delete=models.PROTECT, related_name='courses_taught')
institute = models.ForeignKey(Learning_Institute, on_delete=models.PROTECT, related_name='courses')
I need the instructor field in the Course table to be limited to the set of users in Learning_Institute instead of all the users in the system.
How do I achieve this on the DB level?
I don't think that you can limit in the model itself.
One of the things that you can do is on form save to have validations using form clearing methods like so
And you can create a check that does something like this:
def clean_ instructor(self):
instructor = self.cleaned_data['instructor']
if instructor.type != "instructor":
raise forms.ValidationError("The user is not instructor!")
Another option is to create another User object that will inherit User and you can call it InstrcutorUsers
I have used this tutorial to extend the user model in django
I don't know if it's suitable for your scenario but changing the relations slightly may achieve what you want.
Removing the many to many for User and create a concrete association model for it, will
at least make sure the Course can only have users that also are instructors, by design.
Consider the following model structure:
class LearningInstitute(models.Model):
name = models.TextField()
class InstituteInstructor(models.Model):
class Meta:
unique_together=('user','institute')
user = models.ForeignKey(User, on_delete=models.PROTECT)
institute = models.ForeighKey(LearningInstitute, on_delete=models.PROTECT)
class Course(models.Model):
title = models.CharField(max_length=50)
instructor = models.ForeignKey(InstituteInstructor, on_delete=models.PROTECT)
You have LearningInstitutes
A user can be an instructor with a related institute, a User can only be related to the same institute once
A Course can only have an instructor (and by that also the related institute)
Design can easily be extended to let Courses have multiple instructors.
By design the Course can only have users that are also instructors.
There is a possibility in Django to achieve this in your model class. The option that can be used in models.ForeignKey is called limit_choices_to.
First I'd very strongly recommend to rename the field user in the class LearningInstitute to users. It is a many to many relation, which means an institute can have many users, and a user can perform some work in many institutes.
Naming it correctly in plural helps to better understand the business logic.
Then you can adapt the field instructor in the class Course:
instructor = models.ForeignKey(
'User', # or maybe settings.AUTH_USER_MODEL
on_delete=models.PROTECT,
related_name='courses_taught',
limit_choices_to=~models.Q(learning_institute_set=None)
)
This is not tested and probably will need some adjustment. The idea is to get all User objects, where the field learning_institute_set (default related name, since you haven't specified one) is not (the ~ sign negates the query) None.
This has however nothing to do with normalisation on the database level. The implementation is solely in the application code, and the database has no information about that.
As suggested by #TreantBG, a good approach would be to extend the class User and create class Instructor (or similar). This approach would affect the database by creating an appropriate table for Instructor.

Django Symmetric Many-to-Many In Both Tables

One thing I found frustrating in Django is the seemingly required asymmetry when defining many-to-many relationships. I teach Django and would really like to find the "most elegant" way to describe and teach many-to-many relationships in Django.
One of my students used the technique of putting the class name in as a string in making her many-to-many model. This allows her to avoid less-than-intuitive techniques like related-name. This is my simplified version of her model - the key is that Person and Course are strings, not class names.
class Person(models.Model):
email = models.CharField(max_length=128, unique=True)
courses = models.ManyToManyField('Course', through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
class Course(models.Model):
title = models.CharField(max_length=128, unique=True)
members = models.ManyToManyField('Person', through='Membership')
But while this looks pretty to me, I am concerned that I have messed things up. I have done some basic testing and see no downsides to this style of model definition, but I am concerned that I messed something up that I don't even understand. So I submit this as a question, "What is wrong with this picture?"
There's nothing unusual or controversial about using strings to define a relationship field; this is fully documented. But it doesn't have anything to do with making the relationship symmetrical.
I'm not clear why your student has defined the relationship twice. That seems unnecessary, as does the use of an explicit through table. Your definition is exactly equivalent to this much simpler one:
class Person(models.Model):
email = models.CharField(max_length=128, unique=True)
courses = models.ManyToManyField('Course', related_name='members')
class Course(models.Model):
title = models.CharField(max_length=128, unique=True)
Note there is no need to define anything on Course, and no need for an explicit through table - which, even if it has no extra fields, disables some functionality like inline admin forms. Given a Course object, you can do my_course.members.all() to get the members just as in your version.

Defining a two way many to many in Django

Just starting in Python/Django framework so sorry if this is dumb... but i cant find any solution.
class Dealer(models.Model):
name = models.CharField(max_length=200)
contacts = models.ManyToManyField(Contact)
class Contact(models.Model):
name = models.CharField(max_length=200)
dealers = models.ManyToManyField(Dealer)
I have this relation set up however when I run SyncDB it doesnt work. It tells me that Contact is not defined on this line
contacts = models.ManyToManyField(Contact)
Im more familiar with compiled languages. Is there anyway to tell python that the contact class exists, or better yet is there a special syntax im missing for defining this kind of relation.
I dont see a need for a two way ManyToMany in both the models, as they are a M:N relationship (2 way relationship).
Your issue here is, Contact is not defined at the point of execution of this code:
contacts = models.ManyToManyField(Contact)
So You need to wrap it in quotes for it to work
contacts = models.ManyToManyField('Contact')
Documentation on that can be found here
I would recommend the following models:
class Dealer(models.Model):
name = models.CharField(max_length=200)
contacts = models.ManyToManyField('Contact')
class Contact(models.Model):
name = models.CharField(max_length=200)
and It would do exactly what you are looking for.
You can read about ManyToMany relationships here. The same link also covers how to handle Reverse m2m queries
If you want to do a two way ManyToMany both, you just only need to do this:
class Dealer(models.Model):
name = models.CharField(max_length=200)
contacts = models.ManyToManyField('Contact', blank=True)
class Contact(models.Model):
name = models.CharField(max_length=200)
dealers = models.ManyToManyField('Dealer', through=Dealer.projects.through, blank=True)
I guess it will work to you.

Optional foreign key in model related to a model which doesn't exist

I've attempted to add a second ForeignKey relationship to a Django Model which relates to a model which hasn't yet been created.
class Forms2012(models.Model):
"""
Employer forms for the 2012-13 tax year
"""
employer = models.ForeignKey('Employer', blank=True, null=True)
# employee model is yet to be implemented
employee = models.ForeignKey('Employee', blank=True, null=True)
name = models.CharField(
max_length=50,
choices=constants.FORM_CHOICES)
description = models.CharField(max_length=255)
datemodified = models.DateTimeField()
As you might expect this is giving a not installed or is abstract error. However I've been told it should be possible to validate this because that key is optional.
Could someone explain if this is possible, I've added the flags blank=True, null=True to the FK but model validation fails so I think I'm fighting a losing battle.
Why don't you make (temporary) dummy models?
I would recommend that you implement a "stub" Employee model eg
class Employee(models.Model):
pass
If you are using database migrations ( eg South ), which you should, it should be a simple matter to "fill out" the Employee class later on.
However, if you want a fairly involved "solution" to the problem, you can have a look at the accepted answer in this question. The author of the question & answer admits that it is ugly ( even though it works ). The "stub" solution is better.

Django and reverse relations from foreign keys

suppose the following simple models:
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
author = models.ForeignKey(User, related_name='author_set')
How can I get all the authors that participated in a particular blog (i.e. pk=1)? I tried something like this, but it didn't work.
User.objects.author_set.filter(blog=Blog.objects.get(pk=1))
Many thanks in advance!
User.objects.filter(author_set__blog__pk=1)
I wasn't paying attention to your related name, so the code above (revised) now uses the proper related name. However, this is a really bad related name. The related name should describe what's on the opposite side, i.e. Entry. So really it should be related_name='entry_set'. However, that's the default anyways, so you can remove related_name if you just want that. I would typically use a simple pluralized version of the related class, which would be in this case related_name='entries'. Or, you might want to use something like "blog_entries" to be more specific.