django select related not giving expected result - django

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.

Related

Cannot assign "id": "Product.category" must be a "CategoryProduct" instance

i'm working on a django project and i got this error (Cannot assign "'11'": "Product.category" must be a "CategoryProduct" instance.) anyone here can help me please.
Model:
class Product(models.Model):
name = models.CharField("Nombre", max_length=150)
category = models.ForeignKey(CategoryProduct, on_delete=models.SET_NULL, null=True, related_name='category')
def __str__(self):
return self.name
View:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
def post(self, request, *args, **kwargs):
form = self.get_form()
category = CategoryProduct.objects.get(id=request.POST['category'])
if form.is_valid():
product = form.save(commit=False)
product.category = category
product.save()
Form:
class ProductForm(forms.ModelForm):
name = forms.CharField(max_length=150, label="Nombre")
category = forms.ChoiceField(choices=[(obj.id, obj.name) for obj in CategoryProduct.objects.all()], label="Categoría")
class Meta:
model = Product
fields = ['name', 'category']
You can let Django's ModelForm do its work, this will create a ModelChoiceField [Django-doc], which is where the system gets stuck: it tries to assign the primary key to category, but that should be a ProductCategory object, so you can let Django handle this with:
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'category']
If you want to specify a different label, you can use the verbose_name=… [Django-doc] from the model field, or specify this in the labels options [Django-doc] of the Meta of the ProductForm. So you can specify Categoria with:
class Product(models.Model):
name = models.CharField('Nombre', max_length=150)
category = models.ForeignKey(
CategoryProduct,
on_delete=models.SET_NULL,
null=True,
related_name='products',
verbose_name='Categoria'
)
def __str__(self):
return self.name
then the CreateView can just use its boilerplate logic:
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = '/adminpanel/products/'
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the Category model to the Product
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the category relation to products.

Django list data from dfiferent models in List View with correct ordering

Heys guys.. I am making a Django project which is a simple clone of Twitter.. Got the idea from Justin Mitchell's Udemy course..
So i implemented a Tweet model and a Retweet model which has ForeignKey to the original Tweet and the User..
The thing is that in the homepage i want both the Tweets and Retweets to show and in the order they were created..
I am using Django Rest Framework for the CRUD functionality of Tweet using ModelViewSet
Any idea on how i achieve that using Rest Framework or if that isn't possible could you please give me some other idea..
Thank you in advance..
models.py
class Tweet(models.Model):
content = models.CharField(max_length=140)
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class Meta:
ordering = "-created_on", "content", "user",
def __str__(self):
return self.content
def get_absolute_url(self):
return reverse("tweet_api:tweet-detail", args=[self.id])
class Retweet(models.Model):
tweet = models.ForeignKey(Tweet, on_delete=models.CASCADE, related_name="retweet")
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = "-created_on", "user",
def __str__(self):
return self.tweet.content
serializers.py
class TweetSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
created_on = serializers.SerializerMethodField()
date_display = serializers.SerializerMethodField()
class Meta:
model = models.Tweet
fields = "id", "content", "created_on", "date_display", "user",
def get_created_on(self, obj):
return obj.created_on.strftime("%I:%M %p - %d %b %Y")
def get_date_display(self, obj):
obj_date = obj.created_on
days = (timezone.datetime.now() - obj_date).days
if days > 0:
return obj_date.strftime("%d %b")
else:
return naturaltime(obj_date)
class RetweetSerializer(serializers.ModelSerializer):
tweet = TweetSerializer()
user = UserSerializer(read_only=True)
date_display = serializers.SerializerMethodField()
class Meta:
model = models.Retweet
fields = "id", "tweet", "user", "created_on", "date_display",
def get_date_display(self, obj):
obj_date = obj.created_on
days = (timezone.datetime.now() - obj_date).days
if days > 0:
return obj_date.strftime("%d %b")
else:
return naturaltime(obj_date)
views.py
class TweetViewSet(ModelViewSet):
serializer_class = serializers.TweetSerializer
queryset = models.Tweet.objects.all()
pagination_class = DefaultPagination
filter_backends = filters.SearchFilter,
search_fields = "content", "user__username", "user__first_name", "user__last_name",
def perform_create(self, serialiazer):
return serialiazer.save(user=self.request.user)
class RetweetViewSet(ModelViewSet):
serializer_class = serializers.RetweetSerializer
queryset = models.Retweet.objects.all()
pagination_class = DefaultPagination
filter_backends = filters.SearchFilter,
search_fields = "tweet__content", "user__username", "user__first_name", "
As Tweet and Retweet data reside on completely different models, what you want to do is not simple with your current model structure. To combine them in a single view, you'd need to override many things on ViewSet, and do the operations like sorting in-memory, which would not be scalable. One possible solution could be to use model inheritance, have a base model for both Tweet and Retweet, and build your serialzier and view set on that model. A model structure like the following could be used:
class Post(models.Model):
"""
Base model for user posts
"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
class Tweet(Post):
content = models.CharField(max_length=140)
updated_on = models.DateTimeField(auto_now=True)
class Retweet(Post):
tweet = models.ForeignKey(Tweet, on_delete=models.CASCADE, related_name="retweet")
With these models in-place, you can create a PostSerializer and PostViewSet, and use these only for listing posts, you can keep using Tweet and Retweet views and serializers for creating and updating.

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 to limit choices to Foreign keys in Django admin

I run into a problem when using the Django admin. I'm building a small ScrumBoard. It has projects, with statuses, stories and tasks.
Consider the following model:
#python_2_unicode_compatible
class Project(models.Model):
name = models.CharField(max_length=100)
class Meta:
verbose_name = _('Project')
verbose_name_plural = _('Projects')
def __str__(self):
return self.name
#python_2_unicode_compatible
class Status(models.Model):
name = models.CharField(max_length=64) # e.g. Todo, In progress, Testing Done
project = models.ForeignKey(Project)
class Meta:
verbose_name = _('Status')
verbose_name_plural = _('Statuses')
def __str__(self):
return self.name
#python_2_unicode_compatible
class Story(models.Model):
"""Unit of work to be done for the sprint. Can consist out of smaller tasks"""
project = models.ForeignKey(Project)
name=models.CharField(max_length=200)
description=models.TextField()
status = models.ForeignKey(Status)
class Meta:
verbose_name = _('Story')
verbose_name_plural = _('Stories')
# represent a story with it's title
def __str__(self):
return self.name
The problem: when an admin user creates a story he will see statuses from all the projects instead of the status from one project.
To filter statuses by project, you need your story to already exist so django know which project we are talking about. If you set status nullalble, you can do like this (implying, you do save and continue on first save to set status)
class StatusAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(StatusAdmin, self).get_form(request, obj, **kwargs)
if obj and obj.project:
form.base_fields['status'].queryset = \
form.base_fields['status'].queryset.filter(project=obj.project)
elif obj is None and 'status' in form.base_fields: # on creation
del form.base_fields['status']
return form
You will need something like django-smart-selects

Django: querying multiple types of objects

I have a fairly basic model that allows users to create posts of different 'types'. There's currently a Text type and a Photo type that inherits from a base 'Post' type.
I'm currently pulling TextPosts and PhotoPosts and chaining the two QuerySets, but this seems like a bad idea.
Is there a way to simply query for both types of posts at once? The reason I'm not using .filter() on Post itself is because I (presumably) don't have any way of getting the TextPost or PhotoPost object from it (or do I?)
PS: Does it make more sense to call it BasePost or Post if I'll never be using Post by itself?
class Post(AutoDateTimeModel):
POST_TYPES = (
# Linkable Social Networks
('TEXT', 'Text'),
('PHOTO', 'Photo'),
('LINK', 'Link'),
)
post_type = models.ForeignKey(ContentType)
user = models.ForeignKey(User, blank=True, null=True)
interests = models.ManyToManyField(Interest, related_name='interests')
class Meta:
app_label = 'posts'
ordering = ('-created_at',)
def save(self, *args, **kwargs):
if not self.pk:
self.post_type = ContentType.objects.get_for_model(type(self))
# import pdb; pdb.set_trace()
super(Post, self).save(*args, **kwargs)
class TextPost(Post):
""" Text post model """
body = models.TextField()
class Meta:
app_label = 'posts'
class PhotoPost(Post):
""" Photo post model. This can contain multiple photos. """
description = models.TextField()
class Meta:
app_label = 'posts'
class Photo(models.Model):
""" Individual image model, used in photo posts. """
caption = models.TextField()
# source_url = models.URLField(blank=True, null=True)
image = ImageField(upload_to=upload_to)
post = models.ForeignKey(PhotoPost, blank=True, null=True, related_name='photos')
user = models.ForeignKey(User, blank=True, null=True, related_name='photos')
class Meta:
app_label = 'posts'
def __unicode__(self):
return 'Photo Object by: ' + str(self.user.get_full_name())
You can use this nice app django-model-utils, using InheritanceManager in your Post class.
A nice example from the docs:
from model_utils.managers import InheritanceManager
class Place(models.Model):
# ...
objects = InheritanceManager()
class Restaurant(Place):
# ...
class Bar(Place):
# ...
nearby_places = Place.objects.filter(location='here').select_subclasses()
for place in nearby_places:
# "place" will automatically be an instance of Place, Restaurant, or Bar
Applying for your situation:
class Post(AutoDateTimeModel):
...
objects = InheritanceManager()
class TextPost(Post):
...
class PhotoPost(Post):
...
And this answers your question: Is there a way to simply query for both types of posts at once?
You can query for posts now, resulting instances of TextPost and Photoposts