I'm developing a Django application on Google Appengine, and I came across a db design problem where multi-table inheritance seems to be the best solution. Unfortunately, however, multi-table inheritance is not supported on Appengine because it requires JOINs. I'm looking for an alternative solution that meets the following requirements:
** See updates at bottom **
There are 3 different user types, or profiles:
Business (i.e. owner)
Employee
Client
These profiles share certain attributes, but also have unique attributes for their respective types. For example, all profiles have a contact email and phone number, but only Businesses need to provide a logo or specify their business type.
In addition, I need to be able to retrieve Profile objects from the db (regardless of type), and get the extended or child profile for each (Business, Employee, or Client). Also, a Business, Employee, or Client object should also be able to access the parent profile easily. In other words, the relationship needs to work in both direction (like profile.employee or employee.profile).
So far, I have come up with two possible solutions:
OneToOneField in child model:
class Profile(models.Model):
# Profile may exist before user claims it
user = models.OneToOneField(User, blank=True, null=True)
email ...
phone ...
... other common fields ...
class Business(models.Model):
profile = models.OneToOneField(Profile, verbose_name="user profile", related_name="biz_profile")
class Employee(models.Model):
profile = models.OneToOneField(Profile, verbose_name="user profile", related_name="employee_profile")
class Client(models.Model):
profile = models.OneToOneField(Profile, verbose_name="user profile", related_name="client_profile")
This will allow me to do the following: profile.biz_profile and biz.profile
Unique Generic Foreign Key in parent model:
class Profile(models.Model):
content_type=models.ForeignKey(ContentType)
object_id=models.PositiveIntegerField()
content_object=generic.GenericForeignKey('content_type','object_id')
email ...
phone ...
... other common fields ...
class Meta:
unique_together = ('content_type', 'object_id')
class Business(models.Model):
profiles = generic.GenericRelation(Profile)
class Employee(models.Model):
profiles = generic.GenericRelation(Profile)
class Client(models.Model):
profiles = generic.GenericRelation(Profile)
This will allow me to do the following: profile.content_object and biz.profiles.all()[0]
The first approach (OneToOneField) seems the most straight forward, but I would need to come up with a better way to know which child to call, perhaps by setting the content_type in the Profile model creating a method like:
def get_instance(self):
# Need to look at contenttype framework, but you get the idea
if self.content_type == 'business':
return self.biz_profile
elif self.content_type == 'employee':
return self.employee_profile
elif self.content_type == 'client':
return self.client_profile
return None
I'm not set on either of these solutions, so I welcome any alternative solutions or improvements to what I have here.
Thanks in advance!
UPDATE
My original requirements have changed since I first posted. It turns out I only need parent>child access NOT child>parent. In light of this, I'm going to use the unique Generic Foreign Key approach instead. However, I am still looking for an answer to the original question, so don't be shy if you have a solution.
Related
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.
I have some models that represents some companies and their structure. Also all models can generate some Notifications (Notes). User can see own Notes, and, of course, can't see others.
class Note(models.Model):
text = models.CharField(...)
class Company(models.Model):
user = models.ForeignKey(User)
note = models.ManyToManyField(Note, blank='True', null='True')
class Department(models.Model):
company = models.ForeignKey(Company)
note = models.ManyToManyField(Note, blank='True', null='True')
class Worker(models.Model):
department = models.ForeignKey(Department)
note = models.ManyToManyField(Note, blank='True', null='True')
class Document(models.Model)
company = models.ForeignKey(Company)
note = models.ManyToManyField(Note, blank='True', null='True')
The question is how I can collect all Notes for particular user to show them?
I can do:
Note.objects.filter(worker__company__user=2)
But its only for Notes that was generated by Workers. What about another? I can try hardcoded all existing models, but if do so dozen of kittens will die!
I also tried to use backward lookups but got "do not support nested lookups". May be I did something wrong.
EDIT:
As I mentioned above I know how to do this by enumerating all models (Company, Worker, etc. ). But if I will create a new model (in another App for example) that also can generate Notes, I have to change code in the View in another App, and that's not good.
You can get the Notes of a user by using the following query:
For example let us think that a user's id is 1 and we want to keep it in variable x so that we can use it in query. So the code will be like this:
>>x = 1
>>Note.objects.filter(Q(**{'%s_id' % 'worker__department__company__user' : x})|Q(**{'%s_id' % 'document__company__user' : x})|Q(**{'%s_id' % 'company__user' : x})|Q(**{'%s_id' % 'department__company__user' : x})).distinct()
Here I am running OR operation using Q and distinct() at the end of the query to remove duplicates.
EDIT:
As I mentioned above I know how to do this by enumerating all models
(Company, Worker, etc. ). But if I will create a new model (in another
App for example) that also can generate Notes, I have to change code
in the View in another App, and that's not good.
In my opinion, if you write another model, how are you suppose to get the notes from that model without adding new query? Here each class (ie. Department, Worker) are separately connected to Company and each of the classes has its own m2m relation with Note and there is no straight connection to User with Note's of other classes(except Company). Another way could be using through but for that you have change the existing model definitions.
Another Solution:
As you have mentioned in comments, you are willing to change the model structure if it makes your query easier, then you can try the following solution:
class BaseModel(models.Model):
user = models.Foreignkey(User)
note = models.ManyToManyField(Note)
reports_to = models.ForeignKey('self', null=True, default=None)
class Company(BaseModel):
class Meta:
proxy = True
class Document(BaseModel):
class Meta:
proxy = True
#And so on.....
Advantages: No need to create separate table for document/company etc.
object creation:
>>c= Company.objects.create(user_id=1)
>>c.note.add(Note.objects.create(text='Hello'))
>>d = Document.objects.create(user_id=1, related_to=c)
>>d.note.add(Note.objects.create(text='Hello World'))
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 have some models that relates to User, but does not have a related name on user:
class Registration(models.Model):
user = models.OneToOneField('auth.User', related_name='+')
class ManyToOneModel(models.Model):
user = models.ForeignKey('auth.User', related_name='+')
I would like to make a serializer for User, which can have this as a nested resource. Is there a way to specify what the queryset/object is? This is an example of what I have - and it completly expectedly failes with 'User' object has no attribute 'registration':
class UserSerializer(serializers.Serializer):
pk = serializers.Field()
registration = RegistrationSerializer()
many_to_one_model = ManyToOneModelSerializer(many=True, required=False)
I guess you'd need to manually query for the related objects and then construct the serializers by hand. You'd then construct the final representation and pass that as the data parameter to a Response object.
It seems like you're making life difficult though. If you just define the related_name on your related models you could use ModelSerializer (or HyperlinkedModelSerializer) and it would all Just Work™. — Is there some reason why you can't do this?
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.