I have what i think is a simple question but I am struggling to find out how it works. I get how related name works for foreign keys but with many to many fields it seems to break my brain.
I have two 3 models at play here. A User, TeamMember and Team Model as seen below.
User model is the built in django model.
#TeamMember Model
class TeamMember(models.Model):
member = models.ForeignKey(User, on_delete=models.SET(get_default_team_member), verbose_name='Member Name', related_name="team_members")
...
#Team Model
class Team(models.Model):
name = models.CharField(max_length=50)
manager = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="managers", null=True, blank=True)
team_lead = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="tls", null=True, blank=True)
tps = models.ForeignKey(TeamMember, on_delete=models.SET_NULL, related_name="tps", null=True, blank=True)
members = models.ManyToManyField(TeamMember, blank=True, related_name="members")
...
Now in a view i want to access a specific users team. I thought i could do this by doing something like this:
member = TeamMember.objects.get(pk=1)
member_team = member.members.name
However if I print member_name than it prints nothing. If I try to access any of the other fields on that model like member.members.team_lead.first_name it fails to find the team_lead field. I understand that this has a .all() attribute but i thought it was tied to the team object through the members field. So if that member matches the team it would give me the team. So I thought it might be an issue if the same member was linked to more than one team (which is possible) so i tired something like this member.members.all().first().name and i get an error that states it cannot get name from NoneType.
Is there an easy way to get the team name from a relationship like this or am i better off just doing a team query with the user?
Thanks,
jAC
First of all, I would like to point out that you are not using the related_name (and related_query_name parameters in a proper way). I think this SO post will help you to understand the concept in a better way.
So, I would change the related_name (and related_query_name) values in the Team model as below,
class Team(models.Model):
name = models.CharField(max_length=50)
manager = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
team_lead = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
tps = models.ForeignKey(
TeamMember,
on_delete=models.SET_NULL,
related_name="teams",
related_query_name="team",
null=True,
blank=True,
)
members = models.ManyToManyField(
TeamMember, blank=True, related_name="teams", related_query_name="team"
)
...
Now in a view i want to access a specific user's team.
Since the Team and TeamMember models are connected via ManyToManyField, you may have "zero or more" Teams associated with a single TeamMember
So, the following query will get you all the teams associated with a particular TeamMemeber
team_member = TeamMember.objects.get(pk=1)
all_teams = team_member.teams.all()
You can also iterate over the QuerySet as,
team_member = TeamMember.objects.get(pk=1)
for team in team_member.teams.all():
print(team.name)
For anyone wondering what I did based on JPG's advice was the for loop option
team_member = TeamMember.objects.get(pk=1)
teams = [t.name for t in team_member.members.all()]
I personally do not care which team i get as my need in this case is just to pass a team through even if it is none. So i just use team = team[0] if teams.count() > 0 else "No team"
Related
I have a couple of models that have need of a common set of fields. It is basically a set of various different types of contacts:
# models I would like to share
class Address(models.Model):
label = models.CharField(_('label'), max_length=50, blank=True)
street1 = models.CharField(_('street1'), max_length=125, blank=True)
street2 = models.CharField(_('street2'), max_length=125, blank=True)
city = models.CharField(_('city'), max_length=50, blank=True)
state = models.CharField(_('state'), max_length=2, blank=True)
zip_code = models.CharField(_('zip_code'), max_length=10, blank=True)
class Phone(models.Model):
label = models.CharField(_('label'), max_length=50, blank=True)
phone = models.CharField(_('phone'), max_length=50, blank=True)
# these are the models that I would like to have addresses and phone numbers
class Contact(models.Model):
name = models.CharField(_('name'), max_length=50, blank=False)
class Organization(models.Model):
name = models.CharField(_('name'), max_length=255, blank=False)
email = models.CharField(_('email'), max_length=100, blank=True)
website = models.CharField(_('website'), max_length=255, blank=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
photo = models.CharField(_('photo'), max_length=255, blank=True)
I would like to share the Phone and Address models with the Contact, Organization and UserProfile models. My first attempt was to add ForeignKey on each of Contact, Organization and UserProfile but after more research I believe this is backwards, so I moved the ForeignKey to Address and Phone but then discovered that ForeignKey can belong to one and only one model. In addition to sharing this data structure between multiple different contact types, I would like the ability to add more than one address or phone number to a contact. A contact could have a home address, work address, mobile number, and work number for example. So I have basically 2 questions:
1) Is sharing a model in this way a reasonable thing to do?
2) How would I go about setting up the models?
If I've understood correctly; you want your Contact, Organization, and UserProfile models to all have fields for Address and Phone. Moreover, they can each have more than one address/phone.
Is this reasonable? Sounds so to me.
How could you go about setting up the models? Generic Relations spring to my mind.
Consider just Contact and Address for now. Your second attempt is correct: we have a Many-to-one relationship (one contact, many addresses), so we need to make Contact a ForeignKey field in the Address model, like so:
class Address(models.Model):
<other_fields>
contact = models.ForeignKey('Contact', on_delete=models.CASCADE)
This allows you to assign multiple addresses to each contact.
Moving on, you essentially want your Address model to have multiple foreign keys: Contact, Organization, and UserProfile. One way of achieving this is to use Generic Relations. This makes use of Django's built-in "contenttypes" framework, and allows you to create GenericForeignKey fields that point to more than one model. I encourage you to read the docs linked, since generic relations aren't so trivial. In the end, you'll have something like:
class Address(models.Model):
label = models.CharField(max_length=50, blank=True)
street_1 = models.CharField(max_length=125, blank=True)
street_2 = models.CharField(max_length=125, blank=True)
etc...
models_with_address = models.Q(app_label='app_name', model='contact') | \
models.Q(app_label='app_name', model='organization') | \
models.Q(app_label='app_name', model='userprofile')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to=models_with_address)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
With this, you can create several addresses for each of the models specified in the models_with_address query. To be able to query the addresses for a given contact/organization/etc., you'll need a reverse generic relation. Setting this up involves adding the line address = GenericRelation(Address) to the respective models.
For further generalisation, you could create a ContactableModel (or whatever) class:
class ContactableModel(models.Model):
address = GenericRelation('Address')
phone = GenericRelation('Phone')
Any model with an address and phone number (Contact, Organization, etc.) could then inherit this so that you don't have to repeatedly include those two fields. You could also improve the models_with_address limit, so that we have something like limit_choices_to=<subclasses_of_ContactableModel>.
Hope this helps!
A simple solution would be to add ManyToManyField(Address|Phone) (i.e. two M2M fields)
to Contact, Organization and UserProfile.
But that would mean different contacts/organizations/users could share addresses.
While this looks tempting, the problem with such a solution is that if someone
edits an address of a contact, they also change the address for all the remaining objects in the system!
Another possible solution which avoids the above problem
and doesn't require M2M fields or generic relationships would be to use
multi-table inheritance:
class Address(models.Model):
entity = models.ForeignKey(Entity)
...
class Entity(models.Model):
pass
class Contact(Entity):
...
class Organization(Entity):
...
(Under the hood Django creates implicit OneToOneField-s
that point from Contact & Organization to Entity.)
I'm currently building a website with the Django Framework. I want on the homepage of my website to display all posts made by people the user is following. Here are the classes for Profile, Story and Follow:
class Profile(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=30, null=True)
last_name = models.CharField(max_length=30, null=True)
class Follow(models.Model):
following = models.ForeignKey('Profile', on_delete=models.CASCADE, related_name="following")
follower = models.ForeignKey('Profile', on_delete=models.CASCADE, related_name="follower")
follow_time = models.DateTimeField(auto_now=True)
class Story(models.Model):
author = models.ForeignKey('accounts.Profile', on_delete=models.CASCADE, related_name="author")
title = models.CharField(max_length=50)
content = models.TextField(max_length=10000)
As you can see Follow uses two Foreign Keys to represent the following and the follower. Is there a way to query all stories from people the user is following?
I really don't know what to filter. Or is this maybe a job for aggregation? If someone could help me, that would be awesome!
following_feed = Story.object.filter(???).order_by('-creation_date')
One can use double underscores (__) to look "through" relations (like ForeignKeys, etc.).
So here we can filter like:
Story.objects.filter(
author__following__follower=my_profile
)
So by using author we obtain a reference to the Profile of the author, then with following we look at the Follow model and then finally with follower we again obtain a reference to Profile(s): the profile(s) of the follower(s).
my_profile of course need to be substituted with a Profile object (the profile of the person that is a follower of the authors of the Storys you wish to obtain).
This will generate a query like:
SELECT s.*
FROM story AS s
JOIN follow AS f ON f.following_id = s.author_id
WHERE f.follower_id = 123
where 123 is the id of the my_profile.
If a person is following another person multiple times (here this can happen since you do not enforce that the follower, following tuples are unique in the Follow model), then the corresponding Storys will be yielded multiple times.
It is therefore probably better to add a unique_together constraint in the Follow model:
class Follow(models.Model):
following = models.ForeignKey(
'Profile',
on_delete=models.CASCADE,
related_name="following"
)
follower = models.ForeignKey(
'Profile',
on_delete=models.CASCADE,
related_name="follower"
)
follow_time = models.DateTimeField(auto_now=True)
class Meta:
unique_together = (('following', 'follower'), )
It might also be worth to see the Follow model as the through model of a ManyToManyField [Django-doc].
Note that I haven't tested the code I am posting so tell me if something is missing.
First, you need to get all the Profiles that your users follow. Then you have to get the Stories that they have.
followed_people = Follow.objects.filter(follower=current_user).values('following')
stories = Story.objects.filter(author__in=followed_people)
I have an Event model. Events can have many 'presenters'. But each presenter can either 1 of 2 different types of profiles. Profile1 and Profile2. How do I allow both profiles to go into presenters?
This will be 100% backend produced. As to say, admin will be selecting "presenters".
(Don't know if that matters or not).
class Profile1(models.Model):
user = models.ForeignKey(User, null=True, unique=True)
first_name = models.CharField(max_length=20, null=True, blank=True)
last_name = models.CharField(max_length=20, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
about = models.TextField(null=True, blank=True)
tags = models.ManyToManyField(Tag, null=True, blank=True)
country = CountryField()
avatar = models.ImageField(upload_to='avatars/users/', null=True, blank=True)
score = models.FloatField(default=0.0, null=False, blank=True)
organization = models.CharField(max_length=2, choices=organizations)
class Profile2(models.Model):
user = models.ForeignKey(User, null=True, unique=True)
first_name = models.CharField(max_length=20, null=True, blank=True)
last_name = models.CharField(max_length=20, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
about = models.TextField(null=True, blank=True)
tags = models.ManyToManyField(Tag, null=True, blank=True)
country = CountryField()
avatar = models.ImageField(upload_to='avatars/users/', null=True, blank=True)
score = models.FloatField(default=0.0, null=False, blank=True)
...
class Event(models.Model):
title = models.CharField(max_length=200)
sub_heading = models.CharField(max_length=200)
presenters = ManyToManyField(Profile1, Profile2, blank=True, null=True) ?
...
# I've also tried:
profile1_presenters = models.ManyToManyField(Profile1, null=True, blank=True)
profile2_presenters = models.ManyToManyField(Profile2, null=True, blank=True)
# is there a better way to accomplish this?...
I think you have a desing problem here. In my opinion, you must think what is a Presenter and what's the different between a Presenter with "profile 1" and with "profile 2". What are you going to do with this models? Are you sure there are just two profiles? Is there any chance that, in some time from now, a different profile ("profile 3") appears? And profile 4? and profile N?
I recommend you to think again about your models and their relations. Do NOT make this decision thinking of how difficul/easy will be to handle these models from django admin. That's another problem and i'll bet that if you think your models a little bit, this won't be an issue later.
Nevertheless, i can give you some advice of how to acomplish what you want (or i hope so). Once you have think abount how to model these relations, start thinking on how are you going to write your models in django. Here are some questions you will have to answer to yourself:
Do you need one different table (if you are going to use SQL) per profile?
If you cannot answer that, try to answer these:
1) What's the difference between two different profiles?
2) Are there more than one profile?
3) Each presenter have just one profile? What are the chances that this property changes in near future?
I don't know a lot about what you need but i think the best option is to have a model "Profile" apart of your "Presenter" model. May be something like:
class Profile(models.Model):
first_profile_field = ...
second_profile_field = ...
# Each presenter have one profile. One profile can "represent"
# to none or more presenters
class Presenter(models.Model):
first_presenter_field = ....
second_presenter_field = ....
profile = models.ForeignKey(Profile)
class Event(models.Model):
presenters = models.ManyToManyField(Presenter)
....
This is just an idea of how i imagine you could design your model. Here are some links that may help you once you have design your models correctly and have answered the questions i made to you:
https://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance
https://docs.djangoproject.com/en/dev/misc/design-philosophies/#models
http://www.martinfowler.com/eaaCatalog/activeRecord.html
And to work with the admin once you decide how your design will be:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/
EDIT:
If i'm not wrong, the only difference between profile 1 and 2 fields is the "organization" field. Am i right? So i recommend you to merge both models since they are almost the same. If they have different methods, or you want to add different managers or whatever, you can use the proxy option of django models. For example, you can do this:
class Profile(models.Model):
#All the fields you listed above, including the "organization" field
class GoldenProfile(models.Model):
#you can define its own managers
objects = GoldenProfileManager()
....
class Meta:
proxy = True
class SilverProfile(models.Model):
....
class Meta:
proxy = True
This way, you can define different methods or the same method with a different behaviour in each model. You can give them their own managers, etcetera.
And the event class should stay like this:
class Event(models.Model):
title = models.CharField(max_length=200)
sub_heading = models.CharField(max_length=200)
presenters = ManyToManyField(Profile, blank=True, null=True)
Hope it helps!
Firstly, I know how to fix the problem, I'm just trying to understand why it's occuring. The error message:
users.profile: Reverse query name for field 'address' clashes with related field 'Address.profile'. Add a related_name a
rgument to the definition for 'address'.
And the code:
class Address(models.Model):
country = fields.CountryField(default='CA')
province = fields.CAProvinceField()
city = models.CharField(max_length=80)
postal_code = models.CharField(max_length=6)
street1 = models.CharField(max_length=80)
street2 = models.CharField(max_length=80, blank=True, null=True)
street3 = models.CharField(max_length=80, blank=True, null=True)
class Profile(Address):
user = models.ForeignKey(User, unique=True, related_name='profile')
primary_phone = models.CharField(max_length=20)
address = models.ForeignKey(Address, unique=True)
If I understand correctly, this line:
address = models.ForeignKey(Address, unique=True)
Will cause an attribute to be added to the Address class with the name profile. What's creating the other "profile" name?
What if I don't need a reverse name? Is there a way to disable it? Addresses are used for a dozen things, so most of the reverse relationships will be blank anyway.
Is there a way to copy the address fields into the model rather than having a separate table for addresses? Without Python inheritance (this doesn't make sense, and if an Model has 2 addresses, it doesn't work).
in the django docs it says:
If you'd prefer Django didn't create a backwards relation, set related_name to '+'. For example, this will ensure that the User model won't get a backwards relation to this model:
user = models.ForeignKey(User, related_name='+')
but I never tried it myself....
I'm not sure where the errant profile field is coming from… But one way to find out would be: temporary remove address = models.ForeignKey(…) from Profile, ./manage.py shell, from ... import Address then see what Address.profile will tell you.
I don't think there is any official way to inherit only the fields from some other Model without using inheritance… But you could fake it like this (where SourceModel is, eg, Address and TargetModel is, eg, Profile):
for field in SourceModel._meta.fields:
TargetModel.add_to_class(field.name, copy.deepcopy(field))
(this is coming from Django's ModelBase __new__ implementation)
I don't think it's possible to disable the reverse name.
I've just done a quick grep over the code and it doesn't look like there is any logic which will bypass setting up the related_name field on the related model.
For Example: Add just '+'
class GeneralConfiguration(models.Model):
created_at = models.DateTimeField(editable=False, default=settings.DEFAULT_DATE)
updated_at = models.DateTimeField(editable=False, default=settings.DEFAULT_DATE)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.PROTECT, related_name='+')
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.PROTECT, related_name='+')
Hope all this makes sense :) I'll clarify via comments if necessary. Also, I am experimenting using bold text in this question, and will edit it out if I (or you) find it distracting. With that out of the way...
Using django.contrib.auth gives us User and Group, among other useful things that I can't do without (like basic messaging).
In my app I have several different types of users. A user can be of only one type. That would easily be handled by groups, with a little extra care. However, these different users are related to each other in hierarchies / relationships.
Let's take a look at these users: -
Principals - "top level" users
Administrators - each administrator reports to a Principal
Coordinators - each coordinator reports to an Administrator
Apart from these there are other user types that are not directly related, but may get related later on. For example, "Company" is another type of user, and can have various "Products", and products may be supervised by a "Coordinator". "Buyer" is another kind of user that may buy products.
Now all these users have various other attributes, some of which are common to all types of users and some of which are distinct only to one user type. For example, all types of users have to have an address. On the other hand, only the Principal user belongs to a "BranchOffice".
Another point, which was stated above, is that a User can only ever be of one type.
The app also needs to keep track of who created and/or modified Principals, Administrators, Coordinators, Companies, Products etc. (So that's two more links to the User model.)
In this scenario, is it a good idea to use Django's multi-table inheritance as follows: -
from django.contrib.auth.models import User
class Principal(User):
#
#
#
branchoffice = models.ForeignKey(BranchOffice)
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalcreator")
modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalmodifier")
#
#
#
Or should I go about doing it like this: -
class Principal(models.Model):
#
#
#
user = models.OneToOneField(User, blank=True)
branchoffice = models.ForeignKey(BranchOffice)
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalcreator")
modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalmodifier")
#
#
#
Please keep in mind that there are other user types that are related via foreign keys, for example: -
class Administrator(models.Model):
#
#
#
principal = models.ForeignKey(Principal, help_text="The supervising principal for this Administrator")
user = models.OneToOneField(User, blank=True)
province = models.ForeignKey( Province)
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(User, editable=False, blank=True, related_name="administratorcreator")
modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="administratormodifier")
I am aware that Django does use a one-to-one relationship for multi-table inheritance behind the scenes. I am just not qualified enough to decide which is a more sound approach.
I'd like to expand on the solution by #thornomad.
Extending Django's User class directly can cause all kinds of trouble with the internal django.auth mechanisms. What I've done in a similar situation is precisely what #thornomad suggests - I made my own UserProfile model linked one-to-one with the Django User model, in which I held additional user data and from which I inherited models for different types of users.
Something to fit what you described:
class UserProfile(models.Model):
user = models.OneToOneField(User, blank=True, related_name='profile')
class Meta:
abstract = True
class PositionHolderUserProfile(UserProfile):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(PositionHolderUserProfile, editable=False, blank=True, related_name="created_users")
modified_by = models.ForeignKey(PositionHolderUserProfile, editable=False, blank=True, related_name="modified_users")
class Principal(PositionHolderUserProfile):
branchoffice = models.ForeignKey(BranchOffice)
class Administrator(PositionHolderUserProfile):
superior = models.ForeignKey(Principal, related_name="subordinates")
province = models.ForeignKey(Province)
class Coordinator(PositionHolderUserProfile):
superior = models.ForeignKey(Administrator, related_name="subordinates")
class Company(UserProfile):
name = models.CharField(max_length=50)
class Product(models.Model):
name = models.CharField(max_length=50)
produced_by = models.ForeignKey(Company)
class Buyer(UserProfile):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
products_bought = models.ManyToManyField(Product)
I recently switched over to using models that inherit off of contrib.auto.models.User. My general observation is that in theory they're great, but sometimes they don't get auto-magically handled like they're supposed to.
I think your decision regarding inheritance vs. OneToOne comes down to this:
Do I want to have Django automatically do something right 95% of the time, and need to debug that other 5%
-OR-
Do I want to do something manually myself 100% of the time
If you haven't seen it, the Scott Barham blog has a great post about inheriting off of User, and also building a custom back end to make sure that your custom object is being returned -- Extending the Django User.
Additionally of interest would be the AutoOneToOne field provided by django-annoying. It's sort of a hybrid of the two approaches here -- there's no inheritance taking place, but Django is taking care of creating the matching OneToOneField if it's not present.
Also, thornomad does make a good point about the redundancy in your models. You could easily implement an abstract class to clean that up as so (assuming you're doing manual OneToOne):
class BaseExtendedUser(models.Model):
user = models.OneToOneField(User, blank=True, related_name='profile')
landline = models.CharField(blank=True, max_length=20)
mobile = models.CharField(blank=True, max_length=20)
created_by = models.ForeignKey(User, editable=False, blank=True, related_name="created_users")
modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="modified_users")
class Meta:
abstract = True
class Administrator(BaseExtendedUser):
province = models.ForeignKey(Province)
class Principal(BaseExtendedUser):
branchoffice = models.ForeignKey(BranchOffice)
I don't think I would inherit the User model, rather use a custom UserProfile - leaving the contrib.auth model alone. With the custom UserProfile model, you could setup a base user profile model that can be a part of all your different user types.
Just looking at it quickly, too, I would look carefully at any models that repeat all the same fields (like your last two Principle and Administrator models). Combining the built in group functionality with the user profile idea may do what you are looking for.
Please consider what happens in the data model when a Coordinator gets promoted to a Principal. I would not use inheritance in this case at all. Please reconsider the previous poster's suggestion "Combining the built in group functionality with the user profile idea may do what you are looking for."
Do you need objects of your user classes to act like an auth.User anywhere? That would be the most obvious reason to use inheritance over OneToOne. One pro of the OneToOne approach would be the ease in which you can change over to another User model, if that's a concern.
The real issue I see with what you have above (by either method) is that there doesn't appear to be anything stopping you from having a Principal object and an Administrator object share the same User. OneToOneField can only guarantee a one-to-one mapping between any two relations.