Django: Reverse M2M query with a "through" model - django

I have the following setup:
class Ride(models.Model):
...
riders = models.ManyToManyField(User, through='RideRiders', null=True, blank=True)
class Meta:
db_table = 'rides'
app_label = 'rides'
class RideRiders(models.Model):
user = models.ForeignKey(User)
ride = models.ForeignKey(Ride)
# Other information
...
class Meta:
db_table = 'rides_riders'
app_label = 'rides'
So, for example, i can get all the rides for a user using the following (is this actually the best way?):
Ride.objects.filter(riders__id=user_id)
I want to be able to get all the users for a particular ride but can't seem to figure out how. This RideRiders model seems to be the sticking point as i can't seem to pass beyond that to the user. For example:
Ride.objects.get(id=ride_id).riders.all()
...gives a list of RideRider objects (i think).
Any suggestions?
EDIT:
To add more context, i'm attempting to fetch the riders for a particular ride as an API call using the Django Rest Framework. This is how i'm using it:
class RideRiders(generics.ListAPIView):
model = User
serializer_class = RiderSerializer
def get_queryset(self):
return Ride.objects.get(id=self.kwargs.get('id')).riders.all()
But this is currently giving me the error which is why i feel it's returning RideRider objects rather than Rider objects.
FieldError: Cannot resolve keyword u'ride' into field. Choices are: date_joined, email, first_name, groups, id, is_active, is_staff, is_superuser, last_login, last_name, logentry, password, user_permissions, username
EDIT 2:
So Ride.objects.get(id=self.kwargs.get('id')).riders.all() is throwing the above error (nothing to do with serializers as thought). If i run Ride.objects.get(id=self.kwargs.get('id')).rideriders_set.all() then it returns a queryset (and testing gives the expected number of records for any particular ride). So, this leaves the original question of how do i return a list of users (rather than RideRider objects)?

The culprit turned out to be app_label = 'rides' in the model classes was incorrect after some refactoring of the overall application. This was clearly stopping Django from making the proper lookups across models as they weren't being registered properly.

Related

How to get my Django template to recognize a model method?

In my django template <p>This user is unsubscribed {{sub.is_unsubscribed}}</p> always displays "This user is unsubcribed False" even when it should show True based on the following models.py
from django.shortcuts import get_object_or_404
class Subscriber(models.Model):
email = models.CharField(max_length=12)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
create_date = models.DateTimeField(auto_now_add=True)
def is_unsubscribed(self):
try:
get_object_or_404(MasterUnsubscriber, unsubcriber_email=self.email)
return True
except:
return False
def __str__(self):
return self.email
class MasterUnsubscriber(models.Model):
unsubscriber_email= models.CharField(max_length=12)
And for structural reasons of my app, I do not want to move the unsubscribe to the Subscriber model as a boolean. How can this be resolved while keeping same model formats.
get_object_or_404 will raise an error if there is no element at all, or when there are multiple such elements, since the underlying implementation is to use .get(..). You can make use of .exists() instead:
class Subscriber(models.Model):
# …
def is_unsubscribed(self):
return MasterUnsubscriber.objects.filter(unsubscriber_email=self.mail).exists()
That being said, I would advice to make use a ForeignKey [Django-doc] to the subscriber, not by matching the email address. This will be inefficient, since search on a non-indexed column is slow, and furthermore if the user later changes their email, then the all of a sudden, these can be subscribed again. Django also has convenient ways to filter or make aggregates on related objects, so you get more out of the Django ORM.

FactoryBoy / Django - OneToOneField duplicate key error

I am writing tests for a large Django application with multiple apps. As part of this process I am gradually creating factories for all models of the different apps within the Django project.
However, I've run into some confusing behavior with FactoryBoy
Our app uses Profiles which are linked to the default auth.models.User model with a OneToOneField
class Profile(models.Model):
user = models.OneToOneField(User)
birth_date = models.DateField(
verbose_name=_("Date of Birth"), null=True, blank=True)
( ... )
I created the following factories for both models:
#factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
user = factory.SubFactory('yuza.factories.UserFactory')
birth_date = factory.Faker('date_of_birth')
street = factory.Faker('street_name')
house_number = factory.Faker('building_number')
city = factory.Faker('city')
country = factory.Faker('country')
avatar_file = factory.django.ImageField(color='blue')
tenant = factory.SubFactory(TenantFactory)
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
username = factory.Faker('user_name')
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.Faker('email')
is_staff = False
is_superuser = False
is_active = True
last_login = factory.LazyFunction(timezone.now)
profile = factory.RelatedFactory(ProfileFactory, 'user')
Which I then run the followings tests for:
class TestUser(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
user = UserFactory()
self.assertTrue(user)
self.assertTrue(user.profile)
self.assertTrue(user.profile.tenant)
class TestProfile(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
profile = ProfileFactory()
self.assertTrue(profile)
All tests in TestUser pass, but the TestProfile fails on the factory initialization ( profile = ProfileFactory()) and raises the following error:
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
Its not clear to me why a duplicate User would already exist, (there should only be one call to create one right?, especially since any interfering signals have been disabled)
My code was based on the example from the FactoryBoy documentation, which also dealt with Users / Profiles that are connected via a OneToOneKey
Does anyone know what I am doing wrong?
Update
As per the suggestions of both Bruno and ivissani I've changed the user line in the ProfileFactory to
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
Now all the tests described above pass successfully!
However I still run into the following issue - when other factories call the UserFactory the
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
still returns.
I've included an example of a factory calling the UserFactory below, buts its happening to every factory that has a user field.
class InvoiceFactory(factory.django.DjangoModelFactory):
class Meta:
model = Invoice
user = factory.SubFactory(UserFactory)
invoice_id = None
title = factory.Faker('catch_phrase')
price_paid = factory.LazyFunction(lambda: Decimal(0))
tax_rate = factory.LazyFunction(lambda: Decimal(1.21))
invoice_datetime = factory.LazyFunction(timezone.now)
Changing the user field on the InvoiceFactory to
user = factory.SubFactory(UserFactory, profile=None)
Helps it pass some of the tests, but eventually runs into trouble since it no longer has a profile associated with it.
Weirdly the following (declaring the user before the factory) DOES work:
self.user = UserFactory()
invoice_factory = InvoiceFactory(user=self.user)
Its not clear to me why I still keep running into the IntegrityError here, calling the UserFactory() now works fine.
I think it's because your ProfileFactory creates a User instance, using the UserFactory which itself tries to create a new Profile instance using the ProfileFactory.
You need to break this cycle, as described in the documentation you link to:
# We pass in profile=None to prevent UserFactory from
# creating another profile (this disables the RelatedFactory)
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
If this doesn't work for you and you need more advanced handling, then I suggest implementing a post_generation hook where you can do more advanced things.
EDIT:
Another option is to tell Factory Boy to not recreate a Profile if there is already one for the User by using the django_get_or_create option:
#factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
django_get_or_create = ('user',)
If you do so, you might be able to remove the profile=None that I suggested before.
EDIT 2:
This might also help, change the UserFactory.profile using a post_generation hook:
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
...
# Change profile to a post_generation hook
#factory.post_generation
def profile(self, create, extracted):
if not create:
return
if extracted is None:
ProfileFactory(user=self)
EDIT 3
I've just realised that the username field in your UserFactory is different from the one in factroy boy's documentation, and it's unique in Django. I wonder if this doesn't cause some old instances to be reused because the username is the same.
You may want to try changing this field to a sequence in your factory:
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
# Change to sequence to avoid duplicates
username = factory.Sequence(lambda n: "user_%d" % n)

Filter M2M in template?

In my model, I have the following M2M field
class FamilyMember(AbstractUser):
...
email_list = models.ManyToManyField('EmailList', verbose_name="Email Lists", blank=True, null=True)
...
The EmailList table looks like this:
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
In the app, the user should only see records that is_active=True and is_managed_by_user=True.
In the Admin side, the admin should be able to add a user to any/all of these groups, regardless of the is_active and is_managed_by_user flag.
What happens is that the Admin assigns a user to all of the email list records. Then, the user logs in and can only see a subset of the list (is_active=True and is_managed_by_user=True). This is expected behavior. However, what comes next is not.
The user deselects an email list item and then saves the record. Since M2M_Save first clears all of the m2m records before it calls save() I lose all of the records that the Admin assigned to this user.
How can I keep those? I've tried creating multiple lists and then merging them before the save, I've tried passing the entire list to the template and then hiding the ones where is_managed_by_user=False, and I just can't get anything to work.
What makes this even more tricky for me is that this is all wrapped up in a formset.
How would you go about coding this? What is the right way to do it? Do I filter out the records that the user shouldn't see in my view? If so, how do I merge those missing records before I save any changes that the user makes?
You might want to try setting up a model manager in your models.py to take care of the filtering. You can then call the filter in your views.py like so:
models.py:
class EmailListQuerySet(models.query.QuerySet):
def active(self):
return self.filter(is_active=True)
def managed_by_user(self):
return self.filter(is_managed_by_user=True)
class EmailListManager(models.Manager):
def get_queryset(self):
return EmailListQuerySet(self.model, using=self._db)
def get_active(self):
return self.get_queryset().active()
def get_all(self):
return self.get_queryset().active().managed_by_user()
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
objects = EmailListManager()
views.py:
def view(request):
email = EmailList.objects.get_all()
return render(request, 'template.html', {'email': email})
Obviously there is outstanding data incorporated in my example, and you are more than welcome to change the variables/filters according to your needs. However, I hope the above can give you an idea of the possibilities you can try.
In your views you could do email = EmailList.objects.all().is_active().is_managed_by_user(), but the loading time will be longer if you have a lot of objects in your database. The model manager is preferred to save memory. Additionally, it is not reliant on what the user does, so both the admin and user interface have to talk to the model directly (keeping them in sync).
Note: The example above is typed directly into this answer and has not been validated in a text editor. I apologize if there are some syntax or typo errors.

Django Filter ForeignKey relationships in get_queryset

I wonder what to do here.
So I have an EmailAddress class which has a ForeignKey relationship to my User class
class EmailAddress(models.Model):
user = models.ForeignKey(allauth_app_settings.USER_MODEL, verbose_name=_('user'))
email = models.EmailField(unique=app_settings.UNIQUE_EMAIL,
verbose_name=_('e-mail address'))
I also have a UserProfile class which has a OneToOneField relationship with the mentioned User like so:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
attribute= models.CharField("attr", max_length=150, blank=False)
Now in admin.py I want to filter the Users so that the current user who is logged in to admintool can only see emails from users who share some attribute. The catch is that the attribute is saved in UserProfile and I need to get there through the email.
Basically I don't know how to access the 'attribute' which is in the UserProfile. I understand that I want to filter users by taking the email finding the user that this email belongs to and then compare his 'attribute' to the user who send the request to decide whether to show it to him or not.
def get_queryset(self, request):
user = [User whos email is in the list]
qs = [the queryset]
[and then]
return qs.filter([filter so the user attribute = the request.user attribute])
Im using allauth btw. in case you wonder what
allauth_app_settings.USER_Model
stands for.
Have a nice day!
You need to use the double-underscore syntax to follow the relationships.
qs.filter(user__userprofile__attribute=request.user.attribute)

how to handle multiple profiles per user?

I'm doing something that doesn't feel very efficient. From my code below, you can probably see that I'm trying to allow for multiple profiles of different types attached to my custom user object (Person). One of those profiles will be considered a default and should have an accessor from the Person class. Storing an is_default field on the profile doesn't seem like it would be the best way to keep track of a default, is it?
from django.db import models
from django.contrib.auth.models import User, UserManager
class Person(User):
public_name = models.CharField(max_length=24, default="Mr. T")
objects = UserManager()
def save(self):
self.set_password(self.password)
super(Person, self).save()
def _getDefaultProfile(self):
def_teacher = self.teacher_set.filter(default=True)
if def_teacher: return def_teacher[0]
def_student = self.student_set.filter(default=True)
if def_student: return def_student[0]
def_parent = self.parent_set.filter(default=True)
if def_parent: return def_parent[0]
return False
profile = property(_getDefaultProfile)
def _getProfiles(self):
# Inefficient use of QuerySet here. Tolerated because the QuerySets should be very small.
profiles = []
if self.teacher_set.count(): profiles.append(list(self.teacher_set.all()))
if self.student_set.count(): profiles.append(list(self.student_set.all()))
if self.parent_set.count(): profiles.append(list(self.parent_set.all()))
return profiles
profiles = property(_getProfiles)
class BaseProfile(models.Model):
person = models.ForeignKey(Person)
is_default = models.BooleanField(default=False)
class Meta:
abstract = True
class Teacher(BaseProfile):
user_type = models.CharField(max_length=7, default="teacher")
class Student(BaseProfile):
user_type = models.CharField(max_length=7, default="student")
class Parent(BaseProfile):
user_type = models.CharField(max_length=7, default="parent")
First of all you could make things a lot more easy by not declaring the BaseProfile abstract:
from django.db import models
from django.contrib.auth.models import User, UserManager
class Person(User):
public_name = models.CharField(max_length=24, default="Mr. T")
objects = UserManager()
def save(self):
self.set_password(self.password)
super(Person, self).save()
def _getDefaultProfile(self):
try:
return self.baseprofile_set.get(default=True)
except ObjectDoesNotExist:
return False
profile = property(_getDefaultProfile)
def _getProfiles(self):
return self.baseprofile_set.all()
profiles = property(_getProfiles)
class BaseProfile(models.Model):
person = models.ForeignKey(Person)
is_default = models.BooleanField(default=False)
class Teacher(BaseProfile):
user_type = models.CharField(max_length=7, default="teacher")
class Student(BaseProfile):
user_type = models.CharField(max_length=7, default="student")
class Parent(BaseProfile):
user_type = models.CharField(max_length=7, default="parent")
The way this is nicer? Your properties didn't know anyway what type they were returning, so the abstract baseclass only made you have an incredible annoying overhead there.
If you now are wondering how the hell you can get the data from the specific profiles since I made anything returned BaseProfile? You can do something like this:
try:
#note the lowercase teacher referal
print myuser.profile.teacher.someteacherfield
except Teacher.DoesNotExist:
print "this is not a teacher object!"
Also I do hope you didn't use the user_type field solely for this purpose, because django has it built in better as you can see. I also hope you really have some other unique fields in your derived profile classes because otherwise you should throw them away and just past a usertype field into BaseProfile (look at choices to do this good).
Now as for the is_default, imho this method is as good as any. You can always try to add custom constraints to your dbms itself, saying there sould be 0 or 1 records containing the same FK and is_default=True (there is no django way to do this). What I also would say is, add a method make_default and in that method make sure the is_default is unique for that person (e.g. by first setting is_default to False on all profiles with the same FK). This will save you a lot of possible sorrow. You can also add this check in the save() method of BaseProfile.
Another way you could do it is by adding a Foreign Key to the Person Model that points to the default Profile. While this will ensure default to be unique on django level, it can also provide denormalization and corruption of your data, even on a more annoying level, so I'm no big fan of it. But again, if you do all adding/removing/updating of profiles through predefined methods (will be more complex now!) you should be safe.
Finally, maybe you have good reasons to inherit from User, but the default way to extend the User functionality is not this, it's described here.