Retrieving multiple rows from seperate model in django relationships - django

I've been struggling with this puzzled for a few hours. Here's a schema of what I'm trying to do.
I have a user model and a profile model, it's a one-to-one relationship, but I'd like to be able to query a user and retrieve all the email addresses (from the User model) for users that share the same company (from the Profile Model). To be fair, my understanding of django is limited, but I went through the serializer relations guide and tried my hands at most approach described there, to no avail. At this point, I'm not even sure I'm on the right path.
So, my understanding of it is
From the user, I need to fetch the profile (a source='profile' approach may work)
From that profile, I need to retrieve the company
From that company, I need to retrieve all the user_id that belongs to that company
From those user_ids, I need to lookup the email fields of all those users
I need to also filter out the email address of the user making the request
Does this make any sense? At this point, I'm trying to accomplish all of that from the serializer, but was unsuccessful. Here are some snippets of code I tried, but I doubt any of them will point towards any form of solution, unfortunately.
class TeamEmailsSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['email']
class UserSerializer(serializers.ModelSerializer):
...
# only one of them was present at a time, but none gave any promising results
test_one = Profile.objects.filter(source=profile.company.id).values_list('user_id', flat=True)
test_one = serializers.RelatedField(source='profile.company.id', read_only=True)
test_one = TeamEmailsSerializer(many=True, read_only=True)
test_one = serializers.PrimaryKeyRelatedField(source='email', queryset=User.objects.filter())
class Meta:
model = User
fields = (
'test_one'
)
I'm grateful for any clue that may lead towards a solution.

First, you should add company FK on your user as well, it will make things much easier for you.
Then you can define a new method on User model:
class User(AbstractBaseUser):
...
def other_users_emails(self):
return self.company.users.exclude(pk=self.id).values_list('email', flat=True)
Then in your serializer add 'other_users_emails' to the fields list.
Alternatively you could modify to_representation method on your serializer and add 'other_users_emails' attribute directly there

Related

How can I access a value from one model and pass to another model in Django?

I have one model called Weight (filled by User input/choice) and another called Enterprise.
class Weight(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="weights")
weight_of_history = models.IntegerField(null=True, blank=True)
class Enterprise(models.Model):
...
The weight is saved, one per user, and replaced everytime the user choose new one.
Inside the Enterprise class, I am creating an property that must get the "weight_of_history" (depending on the user, who has chosen the weight) from Weight class, but the models have no Foreign key or related name between them.
class Enterprise(models.Model):
...
#property
def ranking(self):
weight_of_history = <-- HERE I NEED TO TAKE WEIGHT_HISTORY FROM THE FIRST MODEL
THEN I COULD CALCULATE
How could I do that? Thank you!
You can use django's powerful query functionality and fetch the required objects from the db. Here are the docs that might help you with that. Django docs are amazing, so I would recommend that you read up on queries, models, and forms to have easier time with you project.
For your example we can fetch all the weights for the user in one query by filtering the weights by user. In fact, django ORM allows for chaining filters and you can create really sophisticated queries
class Enterprise(models.Model):
...
#property
def ranking(self):
weight_of_history = Weight.objects.filter(user=some_user)
If you do not know the user beforehand, then you can do inside the view and grab the user that makes the request and filter using this user:
#views.py
user_making_request = request.user
weight_of_history = Weight.objects.filter(user=user_making_request)

Django Model for defining a relationship between two other models and filtering later

So am designing a REST API for a mobile blogging app, in which am trying to implement an upvote feature for the blog posts where what i want to implement is:
Whenever a user U upvotes a particular blog B the client will send a POST request (with username and the blog name) which will define a relationship between them in the Database, so that later when the client wants to know that if a particular user has upvoted a particular blog or not, it can get to know by making a GET request through some filering.
I have tried to create Vote model, in which i define a ManyToMany relationship with both, user and the blog post ( see the Vote Model below for better understanding)
here are my models,
class User(models.Model):
username = models.TextField(max_length=30)
def __str__(self):
return self.username
class Post(models.Model):
headline = models.CharField(max_length=200) # the name of the blog
#some other stuff like body of the blog
.
.
def __str__(self):
return self.headline
The Vote model,
class Vote(models.Model):
user = models.ManyToManyField(User)
post = models.ManyToManyField(Post)
def __str__(self):
return ("%s %s" %(self.user, self.post))
This does not do the job well, e.g.,
It requires the URL of the users and blog post(see below point) in the POST request rather than just the names of each, because of the ManyToMany Relationship.
Even if we ignore the above issue and make the following POST requests with form data two times:
{
"user" = [
"http://127.0.0.1:8000/users/1/"
],
"post" = [
"http://127.0.0.1:8000/posts/1/"
],
}
It creates 2 entries in the database.
And further I still cant figure out how the filtering will work out, i.e. how the client side application will get to know whether there is a relationship between the user and the blog post through a GET request.
I think this is a pretty bad design.
Please suggest a better alternative or improvement(if possible) in my current design.
You should use the Vote as the through=… model [Django-doc] of a many-to-many between Post and User, or even just let Django construct the many-to-many model, since here you did not define an extra field.
You thus can define a method like:
class Post(models.Model):
headline = models.CharField(max_length=200) # the name of the blog
# …
voted_users = models.ManyToManyField(User, related_name='voted_posts')
def __str__(self):
return self.headline
For the given Post primary key, and the given User primary key, you can then create a vote with:
Post.voted_users.through.objects.create(
post_id=post_pk,
user_id=user_pk
)
You thus can for example develop a serializer that works on the Post.voted_users.through model, and that has two ForeignKeys: one to the Post model and one to the User model.

How to reference model joined across foreign key in django admin

I have a django 1.6 app with the following (trimmed for clarity)
classes defined. User is the standard django.contrib.auth User class.
class Event(models.Model):
user = models.ForeignKey(User, related_name='events')
name = models.CharField(max_length=64)
class Profile(models.Model):
user = models.ForeignKey(User, related_name='aprofile')
class MemberProfile(Profile):
pass
Here are my admin classes:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', )
class MemberProfileAdmin(ModelAdmin):
model = MemberProfile
fields = ('user', )
readonly_fields = ('user', )
What I'd like to do is display a read-only list of all events for a given member, or at least profile. Of course joining across the User foreign key seems like the way to go, but I am drawing a blank as to how to accomplish this. Here's a summary of attempts so far.
Define an inline admin on the Event class directly referencing the user field, and add it to the ProfileAdmin:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user' # Fails - fk_name 'user' is not a ForeignKey to <class 'solo365.solo_profile.models.profile.Profile'>
...well, no, it sure isn't. But our User has an 'aprofile' field, so...
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile' # Fails - EventInlineAdmin.fk_name' refers to field 'user__aprofile' that is missing from model 'admin_fk_test.Event'.
Ok, those fields look like they should sync up, but perhaps we need to be a little more aggressive:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile__pk' # Fails - 'EventInlineAdmin.fk_name' refers to field 'user__aprofile__pk' that is missing from model 'admin_fk_test.Event'.
I've also tried messing with formfield_for_foreignkey() and friends in both the inline and the regular model admins, but without that fk_name having a valid value, those methods never get called.
I then considered trying to access the events field directly from a Profile's user:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', 'user__events') # Fails - Unknown field(s) (user__events) specified for Profile. Check fields/fieldsets/exclude attributes of class ProfileAdmin.
What about with a custom formfield_for_foreignkey() method? Sadly that never gets called for anything other than the 'user' field. I've also considered a custom get_formsets() method, but frankly I'm not sure how I could use that without a working EventInlineAdmin.
I could of course define a custom field that simply concatenates all of the events and returns that as a string, but ideally I would prefer something like a fully-featured inline (even read-only) than just a chunk o' text. IOW such a custom field would have a method that (ideally) would return an inline form without requiring any sort of custom template, setting of allow_tags, etc.
Am I doomed to have to create a completely custom Form for the Profile admin class? Or is there a simple way to accomplish what I'm trying to do, that I'm just missing?
Update:
Bonus points if a provided solution works for the MemberProfileAdmin class, not just the ProfileAdmin class.
The relation between User and Profile should be a 1:1 relation which would allow the referencing via user__aprofile. Otherwise, the reverse relation of a foreing key is a queryset because one foreign key can be assigned to multiple instances. This is might be the reason why your code failed.
Change it to:
class Profile(models.Model):
user = models.OneToOneKey(User, related_name='aprofile')
This is a bit like using ForeignKey(unique=True).
To know the attributes, it might help to call dir(model_instance) on the model instance in question, or try around in the Django shell (./manage.py shell).
Also, I've experienced that it might be more confusing to assign a custom related_name like in your case where you would expect one profile by looking at the related name but you would actually get back a queryset.
The generated name in that case would be profile_set, and you would have to call profile_set.all() or profile_set.values() to get some actual profiles.

Transform ForeignKey into OneToOne field depending on current user

I have a setup like in the following (simplified) example:
class Pizza(models.Model):
name = models.CharField(max_length=100)
class Favorite(models.Model):
user = models.ForeignKey(User, related_name='favorites')
pizza = models.ForeignKey(Pizza, related_name='favorites')
class Meta:
unique_together = (('user', 'pizza'),)
I know that I can filter the favorites property on a Pizza model instance by the user property, but I'd like to abstract this.
In my application, a user should only have access to its personal Favorite model instances. To ensure this, I find myself having to filter all the time and do a lot of ugly and inefficient stuff.
I want to abstract this so that when a user is logged in, I should be able to access pizza.favorite, instead of pizza.favorites, which is automatically mapped to the current user's Favorite model for this particular Pizza model. Ideally, I should also be able to filter on this property (no possible with the #property annotation). Basically, it should act like the ForeignKey is now a OneToOne field.
Any ideas on how could I achieve this behaviour? I should note that un-authorized users do not concern me. The application does not provide anonymous access, so that edge case can be disregarded.
I am using Django 1.7 and I am also open to using the development version, if that would help.

Django Admin - Validating inlines together with main models

I have quite a complex validation requirement, and I cannot get Django admin to satisfy it.
I have a main model (django.contrib.auth.models.User) and several models which look like
class SomeProfile(models.Model):
user = models.OneToOneField(User)
# more fields
I want to check that, if the user belongs to some group, then it has the corresponding profile. So if user is in group Foo he should have a non empty FooProfile.
Where do I put this validation rule? I cannot put it in the model. Indeed, the user is not created yet when the form is validated, hence I cannot access his groups. So I need to resort to form validation. This is what I put:
class UserAdminForm(forms.ModelForm):
"""
A custom form to add validation rules which cannot live in the
model. We check that users belonging to various groups actually
have the corresponding profiles.
"""
class Meta:
model = User
def clean(self):
# Here is where I would like to put the validation
class FooInline(admin.TabularInline):
model = FooProfile
max_num = 1
class UserAdmin(admin.ModelAdmin):
model = User
form = UserAdminForm
inlines = [FooInline]
admin.site.register(User, UserAdmin)
My problem is that inside UserAdminForm.clean() I do not have access to the data posted inside the inlines. So I can tell whether the user is in group Foo by inspecting self.cleaned_data['groups'], but I have no way to tell whether a FooProfile was transmitted.
How do I check this validation requirement?
Edit:
I try to explain the issue better, because there has been a misunderstading in an answer.
I have an issue when I create a new user. The fact is that the profiles are mandatory (according to the groups). Say an admin creates a new user; then I have to add inlines in the admin form for the various GroupProfiles.
How do I check that the right profiles are not null? I cannot use the clean() method of the User model, because in there I cannot check what groups the user belongs to: it has not been created yet.
I can only access the information about the groups in the clean() method of the form - but there I do not have the information about the profiles, since this information is submitted trhough inlines.
1
well i have been looking around, how all this stuff works, and i found one question very similar here.
2
There are one way to get all the data at the same time maybe with this you can find the answer to your problem
class UserAdminForm(forms.ModelForm):
"""
A custom form to add validation rules which cannot live in the
model. We check that users belonging to various groups actually
have the corresponding profiles.
"""
class Meta:
model = User
def clean(self):
self.data # <--here is all the data of the request
self.data['groups']
self.data['profile_set-0-comments'] # some field
# some validations
return self.cleaned_data