I've previously asked questions for this project, regarding the Django Admin, Inlines, Generics, etc. Thanks again to the people who contributed answers to those. For background, those questions are here:
Django - Designing Model Relationships - Admin interface and Inline
Django Generic Relations with Django Admin
However, I think I should probably review my model again, to make sure it's actually "correct". So the focus here is on database design, I guess.
We have a Django app with several objects - Users (who have a User Profile), Hospitals, Departments, Institutions (i.e. Education Institutions) - all of whom have multiple addresses (or no addresses). It's quite likely many of these will have multiple addresses.
It's also possible that multiple object may have the same address, but this is rare, and I'm happy to have duplication for those instances where it occurs.
Currently, the model is:
class Address(models.Model):
street_address = models.CharField(max_length=50)
suburb = models.CharField(max_length=20)
state = models.CharField(max_length=3, choices=AUSTRALIAN_STATES_CHOICES)
...
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
...
class Hospital(models.Model):
name = models.CharField(max_length=20)
address = generic.GenericRelation(Address)
...
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
address = generic.GenericRelation(Address)
...
Firstly - am I doing it right, with the FK field on each object that has address(es)?
Secondly, is there a better alternative to using Generic Relations for this case, where different objects all have addresses? In the other post, somebody mentioned using abstract classes - I thought of that, but there seems to be quite a bit of duplication there, since the addresses model is basically identical for all.
Also, we'll be heavily using the Django admin, so it has to work with that, preferrably also with address as inlines.(That's where I was hitting an issue - because I was using generic relationships, the Django admin was expecting something in the content_type and object_id fields, and was erroring out when those were empty, instead of giving some kind of lookup).
Cheers,
Victor
I think what you've done is more complicated than using some kind of subclassing (the base class can either be abstract or not).
class Addressable(models.Model):
... # common attributes to all addressable objects
class Hospital(Addressable):
name = models.CharField(max_length=20)
...
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
class Address(models.Model):
street_address = models.CharField(max_length=50)
suburb = models.CharField(max_length=20)
state = models.CharField(max_length=3, choices=AUSTRALIAN_STATES_CHOICES)
...
owner = models.ForeignKey(Addressable)
You should consider making the base class (Addressable) abstract, if you don't want a seperate table for it in your database.
Related
I am running into a little bit of unique problem and wanted to see which solution fit best practice or if I was missing anything in my design.
I have a model - it has a field on it that represents a metric. That metric is a foreign key to an object which can come from several database tables.
Idea one:
Multiple ForeignKey fields. I'll have the benefits of the cascade options, direct access to the foreign key model instance from MyModel, (although that's an easy property to add), and the related lookups. Pitfalls include needing to check an arbitrary number of fields on the model for a FK. Another is logic to make sure that only one FK field has a value at a given time (easy to check presave) although .update poses a problem. Then theres added space in the database from all of the columns, although that is less concerning.
class MyModel(models.Model):
source_one = models.ForeignKey(
SourceOne,
null=True,
blank=True,
on_delete=models.SET_NULL,
db_index=True
)
source_two = models.ForeignKey(
SourceTwo,
null=True,
blank=True,
on_delete=models.SET_NULL,
db_index=True
)
source_three = models.ForeignKey(
SourceThree,
null=True,
blank=True,
on_delete=models.SET_NULL,
db_index=True
)
Idea two:
Store a source_id and source on the model. Biggest concern I have with this is needing to maintain logic to set these fields to null if the source is deleted. It otherwise seems like a cleaner solution, but not sure if the overhead to make sure the data is accurate is worth it. I can probably write some logic in a delete hook on the fk models to clean MyModel up if necessary.
class MyModel(models.Model):
ONE = 1
TWO = 2
THREE = 3
SOURCES = (
(ONE, "SourceOne"),
(TWO, "SourceTwo"),
(THREE, "SourceThree")
)
source_id = models.PositiveIntegerField(null=True, blank=True)
source = models.PositiveIntegerField(null=True, blank=True, choices=SOURCES)
I would love the communities opinion.
Your second idea seems fragile as the integrity is not ensured by the database as you have pointed out yourself.
Without knowing more about the use case, it's difficult to provide an enlightened advice however if your "metric" object is refered by many other tables, I wonder if you should consider approaching this the other way round, i.e. defining the relationships from the models consuming this metric.
To exemplify, let's say that your project is a photo gallery and that your model represents a tag. Tags could be associated to photos, photo albums or users (e.g.. the tags they want to follow).
The approach would be as follow:
class Tag(models.Model):
pass
class Photo(models.Model):
tags = models.ManyToManyField(Tag)
class Album(models.Model):
tags = models.ManyToManyField(Tag)
class User(AbstractUser):
followed_tags = models.ManyToManyField(Tag)
You may even consider to factor in this relationship in an abstract model as outlined below:
class Tag(models.Model):
pass
class TaggedModel(models.Model):
tags = models.ManyToManyField(Tag)
class Meta:
abstract = True
class Photo(TaggedModel):
pass
As mentioned in the comments, you are looking for a Generic Relation:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class SourceA(models.Model):
name = models.CharField(max_length=45)
class SourceB(models.Model):
name = models.CharField(max_length=45)
class MyModel(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
source = GenericForeignKey('content_type', 'object_id')
There are three parts to setting up a Generic Relation:
Give your model a ForeignKey to ContentType. The usual name for this field is “content_type”.
Give your model a field that can store primary key values from the models you’ll be relating to. For most models, this means a PositiveIntegerField. The usual name for this field is “object_id”.
Give your model a GenericForeignKey, and pass it the names of the two fields described above. If these fields are named “content_type” and “object_id”, you can omit this – those are the default field names GenericForeignKey will look for.
Now you can pass any Source instance to the source field of MyModel, regardless of which model it belongs to:
source_a = SourceA.objects.first()
source_b = SourceB.objects.first()
MyModel.objects.create(source=source_a)
MyModel.objects.create(source=source_b)
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.
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.
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.
Let's say that I have subclassed User model (CustomUser) properly (as explained here: http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/)
and installed the comments app.
to access the user of a comment in the template I write:
{{comment.user}} # which provides User, not my CustomUser
and therefore,
{{comment.user.CustomProperty}} #does not work.
How can I work around it?
The ForeignKey from comments.Comment is to django's built-in User object, so querying comment.user will give you the parent object (ie: the base User model). However, django inheritance does provide a way to get the subclass version from the superclass:
{{ comment.user.customeruser }}
Which would then allow you to do:
{{ comment.user.customeruser.customproperty }}
I happen to think this is a weakness in Django's inheritance implementation since it doesn't exactly mirror the behaviour you'd expect from object inheritance in Python generally, but at least there is a workaround. Since I'm hardly rushing out to submit a patch with my version of the right behaviour, I can't complain :-)
I agree with Carl Meyer's comment: it could expensive to automatically fetch the subclass without altering the parent model's db table, and returning the instance of the subclass from the parent class query would be inconsistent with Django's promise that a queryset returns the model on which the queryset was run.
I still find in practice, however, that Django's inheritance leads to some awkward extra steps on occasion. Having used Django since 0.91, and knowing that all the different strategies for resolving object-relational mapping issues have tradeoffs, I'm very happy to have inheritance in Django now, and feel that the current implementation is excellent... so I'd hate for my original answer to be construed as a slight against the project.
As such, I thought I would edit this answer to link to an answer Carl himself provided on a solution in cases where you don't know what type the subclass is: How do I access the child classes of an object in Django without knowing the name of the child class?. He offers advice there for using the ContentType framework. Still some indirection involved, but a good, generalizable, option in the toolkit.
As you can see in the comments of that post, it is still controversially discussed, what is the best way.
I tried it by subclassing too, but I ran in many problems, while using profiles work perfectly for me.
class IRCUser(models.Model):
user = models.ForeignKey(User, unique=True)
name = models.CharField(max_length=100, blank= True, null = True )
friends = models.ManyToManyField("IRCUser", blank= True, null = True)
dataRecieved= models.BooleanField(default=False)
creating an IRCUser works like this:
>>> IRCUser(user = User.objects.get(username='Kermit')).save()
EDIT: Why are user_profiles elegant:
Let's assume, we are writing a webapp, that will behave as a multi-protocol chat. The users can provide their accounts on ICQ, MSN, Jabber, FaceBook, Google Talk .....
We are free to create a custom user class by inheritance, that will hold all the additional informations.
class CustomUser(User):
irc_username = models.CharField(blank=True, null=True)
irc_password = models.PasswordField(blank=True, null=True)
msn_username = models.CharField(blank=True, null=True)
msn_password = models.PasswordField(blank=True, null=True)
fb_username = models.CharField(blank=True, null=True)
fb_password = models.PasswordField(blank=True, null=True)
gt_username = models.CharField(blank=True, null=True)
gt_password = models.PasswordField(blank=True, null=True)
....
....
this leads to
data-rows with a lot of zero-values
tricky what-if-then validation
the impossibility, to have more the one account with the same service
So now let's do it with user_profiles
class IRCProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='ircprofile')
username = models.CharField()
password = models.PasswordField()
class MSNProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='msnprofile')
username = models.CharField()
password = models.PasswordField()
class FBProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='fbprofile')
username = models.CharField()
password = models.PasswordField()
the result:
User_profiles can be created when needed
the db isn't flooded by zero-values
n profiles of same type can be assigned to one user
validation is easy
this may lead to a more cryptic syntax in the templates, but we are free to have some shortcuts in our views/template_tags or to use {% with ... %} to flavour it as we want.
I don’t think there is a way around it, because as you said, comment.user is a User, not a CustomUser.
See http://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models:
QUERYSETS STILL RETURN THE MODEL THAT WAS REQUESTED
There is no way to have Django return, say, a MyUser object whenever you query for User objects. A queryset for User objects will return those types of objects. The whole point of proxy objects is that code relying on the original User will use those and your own code can use the extensions you included (that no other code is relying on anyway). It is not a way to replace the User (or any other) model everywhere with something of your own creation.
Maybe in this case, the old way of using UserProfile would be a better choice?
Sorry if I’m not helping much.