Django ManyToMany filter - django

I have a Region infrastructure modeled as follows: Each region has a ManyToMany of countries, and optionally states (if it's a region within the US)
from django.contrib.auth.models import User
from django.contrib.localflavor.us.models import USStateField
from django.db import models
from django_countries import CountryField
class CountryManager(models.Manager):
def get_by_natural_key(self, country):
return self.get(country=country)
class Country(models.Model):
country = CountryField(unique=True)
objects = CountryManager()
class Meta:
ordering = ('country',)
def __unicode__(self):
return unicode(self.country.name)
def natural_key(self):
return (self.country.code,)
class StateManager(models.Manager):
def get_by_natural_key(self, state):
return self.get(state=state)
class State(models.Model):
state = USStateField(unique=True)
objects = StateManager()
class Meta:
ordering = ('state',)
def __unicode__(self):
return self.get_state_display()
def natural_key(self):
return (self.state,)
class Region(models.Model):
name = models.CharField(max_length=255, unique=True)
coordinator = models.ForeignKey(User, null=True, blank=True)
is_us = models.BooleanField('Is a US region')
countries = models.ManyToManyField(Country)
states = models.ManyToManyField(State, blank=True)
class Meta:
ordering = ('name',)
def __unicode__(self):
return self.name
Each user has a profile defined (partially) as follows:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='user_profile')
city = models.CharField(max_length=255)
country = CountryField()
state = USStateField(_(u'US only (determines user's region)'), blank=True, null=True)
I'm trying to filter a bunch of user objects by region. So I have
region = Region.objects.filter(id=self.request.GET['filter_region'])
if len(region) == 0:
raise Exception("Region not found for filter")
if len(region) > 1:
raise Exception("Multiple regions found for filter?")
region = region[0]
queryset = queryset.filter(user_profile__country__in=region.countries.all)
Sadly though, this returns an empty queryset. I suspect it has to do with the fact that the "Country" model has a "country" field within it (terrible ambiguous naming, I know, not my code originally), and I'm only filtering by "Country" models, not the "country" fields within them. (Does that make sense?)
How can I filter by a subfield of the ManyToMany field?

First, why are you using .filter() if you want just a single item do:
region = Region.objects.get(id=self.request.GET['filter_region'])
That will raise an ObjectDoesNotExist exception if the object doesn't exist, but you're raising an exception if the queryset is empty anyways. If you need to catch that exception you can either use a try...except block or get_object_or_404 if you're in a view.
Second, don't use self.request.GET['filter_region'] directly. If the key isn't set you'll raise an IndexError. Use, instead:
self.request.GET.get('filter_region')
Now, as to your actual problem: UserProfile.country is a CountryField which is just a specialized CharField. Whereas Region.countries is a M2M with the model Country. The two are not comparable, which is why your queryset is coming back empty.
Make UserProfile.country a foreign key to Country and you're in business.

Related

django select related not giving expected result

I am querying select related between two models Requirements and Badge Requirement has a related badge indicated by badge_id Models are,
class Badge(models.Model):
level = models.PositiveIntegerField(blank=False, unique=True)
name = models.CharField(max_length=255, blank=False , unique=True)
description = models.TextField(blank=True)
class Meta:
verbose_name = _("Badge")
verbose_name_plural = _("Badges")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("Badge_detail", kwargs={"pk": self.pk})
""" Requirement Model for requirements """
class Requirement(models.Model):
number = models.PositiveIntegerField(blank=False)
badge = models.ForeignKey(Badge, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
class Meta:
verbose_name = _("Requirement")
verbose_name_plural = _("Requirements")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("Requirement_detail", kwargs={"pk": self.pk})
In My view I try to join both tables and retrieve. It is,
""" ajax requirements in requirements table """
def get_requirements(request):
requirements = Requirement.objects.all().select_related('badge').values()
print(requirements)
return JsonResponse(list(requirements), safe=False)
The result is,
to the frontend,
to the backend,
Why does it not give me both tables' values?
Best way to achieve that is using Serializers which are the key component to deal with transforming data from models to JSON and the inverse:
To use this approach you can create the following serializers:
yourapp.serializers.py
from rest_framework.serializers import ModelSerializer
from yourapp.models import Requirement, Badge
class BadgeSerializer(ModelSerializer):
class Meta:
model = Badge
fields = '__all__'
class RequirementSerializer(ModelSerializer):
badge = BadgeSerializer()
class Meta:
model = Requirement
fields = '__all__'
After that you should go to your views.py file and do the following changes:
from yourapp.serializers import RequirementSerializer
def get_requirements(request):
reqs = Requirement.objects.select_related('badge')
return JsonResponse(RequirementSerializer(reqs, many=True), safe=False)
In this way you will have a more flexible way to add or remove fields from the serializer, and your application is also going to be more decoupled and easy to maintain.

Unique together in models

I have a model called Company.
In a second model which is Branch, I use Company as a foreign key.
class Branch(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE)
Now in some other model, I want to set a property(name) unique together with the Company but I use the branch as a foreign key.
class ABC(models.Model):
name = models.CharField()
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
class Meta:
unique_together = (
('branch__company', 'name'),
)
Can I do something like the above? It gives me an error that the field is nonexistent. Or can I use both company and branch in my model as foreign key?
class ABC(models.Model):
name = models.CharField()
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
class Meta:
unique_together = (
('company', 'name'),
)
I want to attach ABC object with a branch but if once added it should be unique to that company (other branches of that company can not have the same name).
Read about the circular error and was thinking of the same here.
Unique together will be depreciated in the future but I'm not thinking about this right now.
Any advice?
I suggest you to perform validation in the clean method (without a database constraint):
from django.core.exceptions import ValidationError
class ABC(models.Model):
name = models.CharField()
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
def clean(self):
super().clean()
if ABC.objects.filter(name=self.name, branch__company=self.branch.company).exists():
raise ValidationError('Error message')
def save(self, *args, **kwargs):
# Forces the clean method to be called
self.full_clean()
super().save(*args, **kwargs)

How to skip an existing object instance when creating resources in bulk python

I am trying to create a resources in bulk. While the resources are created I have the matric_no has to be unique. If the value of an existing matric_no is uploaded together with the some new entries, I get an integrity error 500 because the value already exists and it stops the rest of the values from being created. How can I loop through these values and then check if the value exists, and then skip so that the others can be populated? Here is my code:
**models.py**
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
#python_2_unicode_compatible
class Undergraduate(models.Model):
id = models.AutoField(primary_key=True)
surname = models.CharField(max_length=100)
firstname = models.CharField(max_length=100)
other_names = models.CharField(max_length=100, null=True, blank=True)
card = models.CharField(max_length=100, null=True, blank=True)
matric_no = models.CharField(max_length=20, unique=True)
faculty = models.CharField(max_length=250)
department_name = models.CharField(max_length=250)
sex = models.CharField(max_length=8)
graduation_year = models.CharField(max_length=100)
mobile_no = models.CharField(max_length=150, null=True, blank=True)
email_address = models.CharField(max_length=100)
residential_address = models.TextField(null=True, blank=True)
image = models.CharField(max_length=250,
default='media/undergraduate/default.png', null=True, blank=True)
def __str__(self):
return "Request: {}".format(self.matric_no)
***serializers.py***
from .models import Undergraduate
from .models import Undergraduate
class UndergraduateSerializer(serializers.ModelSerializer):
class Meta:
model = Undergraduate
fields ='__all__'
class CreateListMixin:
"""Allows bulk creation of a resource."""
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
print(list)
kwargs['many'] = True
return super().get_serializer(*args, **kwargs)
**api.py**
from .models import Undergraduate
from rest_framework.viewsets import ModelViewSet
from .serializers import CreateListMixin,UndergraduateSerializer
class UndergraduateViewSet(CreateListMixin, ModelViewSet):
queryset = Undergraduate.objects.all()
serializer_class = UndergraduateSerializer
permission_classes = (permissions.IsAuthenticated,)
**urls.py**
from rest_framework.routers import DefaultRouter
from .api import UndergradMassViewSet
router=DefaultRouter()
router.register(r'ug', UndergradMassViewSet)
This is the updated serializer.py
class UndergraduateSerializer(serializers.ModelSerializer):
class Meta:
model = Undergraduate
fields = ('id', 'surname', 'firstname', 'other_names', 'card','matric_no', 'faculty', 'department_name', 'sex', 'graduation_year', 'mobile_no', 'email_address', 'residential_address')
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
This is how i moved it now
Seriliazers.py
class UndergraduateSerializer(serializers.ModelSerializer):
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
class Meta:
model = Undergraduate
fields = ('id', 'surname', 'firstname', 'other_names', 'card','matric_no', 'faculty', 'department_name', 'sex', 'graduation_year', 'mobile_no', 'email_address', 'residential_address')
read_only_fields = ('id',)
When the list sent has an existing matric_no , it returns 500 ListSeriaizer is not iterable
You definitely have to implement a custom create() method in your serializer to handle such a case as the serializer's default create method expects one object.
Nonetheless, there is a couple of design decisions to consider here:
You can use bulk_create but it has a lot of caveats which are listed in the docs and it would still raise an integrity error since the inserts are done in one single commit. The only advantage here is speed
You can loop over each object and create them singly. This will solve the integrity issue as you can catch the integrity exception and move on. Here you'll lose the speed in 1
You can also check for integrity issues in the validate method before even moving on to create. In this way, you can immediately return an error response to the client, with information about the offending ro. If everything is OK, then you can use 1 or 2 to create the objects.
Personally, I would choose 2(and optionally 3). Assuming you also decide to chose that, this is how your serializer's create method should look like:
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
So in this case, only the created objects will be returned as response

How do i show an attribute of an object, rather than the object itself, in an accessor field in django-tables2?

I have a table of Compounds with a name field, which is linked to another table called Names.
When I render a table with django-tables2, it's showing up just fine except for the fact that it doesn't say aspirin in the name column, it says Name object.
models.py:
class Compound(models.Model):
drug_id = models.AutoField(primary_key=True)
drug_name = models.ForeignKey(Name, db_column='drug_name', null=True, on_delete=models.PROTECT)
# for flagging problematic data
flag_id = models.ForeignKey(Flag, db_column='flag_id', null=True, on_delete=models.PROTECT)
# is a cocktail
is_combination = models.BooleanField()
class Meta:
db_table = 'compounds'
tables.py:
import django_tables2 as tables
from .models import Compound
class FimTable(tables.Table):
drug_name = tables.Column(accessor='name.name')
class Meta:
model = Compound
attrs = {'class': 'paleblue table table-condensed table-vertical-center'}
fields = ('drug_id', 'drug_name')
sequence = ('drug_id', 'drug_name')
order_by = ('drug_id')
views.py:
#csrf_protect
#login_required # redirects to login page if user.is_active is false
def render_fim_table(request):
table = FimTable(Compound.objects.all())
table.paginate(page=request.GET.get('page', 1), per_page=20)
response = render(request, 'fim_table.html', {'table': table})
return response
Result:
You just need to define the __str__ method on the Name object.
class Name(models.Model):
...
def __str__(self):
return self.name
you can also use...
class Name(model.Model):
...
def __unicode__(self):
return self.name

Django prefetch_related with m2m through relationship

I have the following models
class Film(models.Model):
crew = models.ManyToManyField('Person', through='Role', blank=True)
class Role(models.Model):
person = models.ForeignKey('Person')
film = models.ForeignKey('Film')
person_role = models.ForeignKey(RoleType)
credit = models.CharField(max_length=200)
credited_as = models.CharField(max_length=100)
class RoleType(models.Model):
"""Actor, director, makeup artist..."""
name = models.CharField(max_length=50)
class Person(models.Model):
slug = models.SlugField(max_length=30, unique=True, null=True)
full_name = models.CharField(max_length=255)
A Film("Star Wars: The Clone Wars") has several Person("Christopher Lee"), each one of them can have one or more Role("Voice of Count Dooku") and every Role has a RoleType("Voice actor").
I'm using a DetailView to display the Film
class FilmDetail(DetailView):
model = Film
In my template i'm showing all the Persons, so each time I show a Film 609 queries are being executed. To reduce this I want to use prefetch_related so I changed the view to:
class FilmDetail(DetailView):
model = Film
def get_queryset(self):
return super(FilmDetail, self).get_queryset().prefetch_related('crew')
But this didn't reduce the number of queries(610), I tried the following parameters to prefetch related and it didn't work:
def get_queryset(self):
return super(FilmDetail, self).get_queryset().prefetch_related('crew__person_role')
I got an Cannot find 'person_role' on Person object, 'crew__person_role' is an invalid parameter to prefetch_related()error
What can I do to prefetch the Person.full_name and slug and all Role fields from Film.crew?
You can construct your queryset like this:
from django.db.models import Prefetch
def get_queryset(self):
return super(FilmDetail, self).get_queryset().prefetch_related(
Prefetch(
'crew',
queryset=Role.objects.select_related(
'person',
'person_role',
),
),
)
Only Film->Role is a backwards relation loadable with prefetch_related. Role->RoleType and Role->Person are forwards relations that you load with select_related.