Annotate django query set on related object queryset's related object count - python-3.x

I have the following three models:
class User(AbstractUser):
username = None
name = models.CharField(max_length=255)
email = models.EmailField(unique=True)
account_type = models.CharField(
choices=AccountType.choices,
default=AccountType.GENERAL,
max_length=8,
)
first_name = None
last_name = None
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["name"]
objects = UserManager()
#property
def total_likes(self):
queryset = (
self.works.filter(visibility=Visibility.PUBLIC)
.select_related()
.annotate(likes=models.Count("social"))
)
return (
queryset.aggregate(models.Sum("likes")).get("likes__sum")
or 0
)
def __str__(self) -> str:
return self.name
class WorkImage(models.Model):
user = models.ForeignKey(
to=User, on_delete=models.CASCADE, related_name="works"
)
title = models.CharField(max_length=255)
image = models.ImageField(upload_to=work_image_directory_path)
#property
def likes(self):
return self.social.count()
def __str__(self) -> str:
return f"{self.user.name}'s {self.work_image_type} {self.title}"
class WorkImageSocial(models.Model):
work = models.ForeignKey(
to=WorkImage, on_delete=models.CASCADE, related_name="social"
)
liked_by = models.ForeignKey(
to=User, on_delete=models.CASCADE, related_name="liked_works"
)
def __str__(self) -> str:
return f"{self.liked_by.name}=>{self.work.title}"
I am writing a custom ordering filter to get the "most liked" users, what I want to do is annotate users with a field called "likes" which will have the sum of all likes on their work images, how do I achieve this?

Related

Django MultiSelectField Selected Choice Yields "Not a Valid Choice" Error

I am new to Django and have a MultiSelectField in my Meal Model. I am also utilizing a MultipleChoiceField with widget CheckBoxSelectMultiple in my Meal Form. When I select a checkbox in the Template and POST the form, I get an error which states, "[ValidationError(["Value ['CHICKEN CASSEROLE'] is not a valid choice."])]}). I am wondering what I am doing wrong here and need some assistance in figuring this out. Any help is appreciated. Below is my code for my Model and Form:
class Meal(models.Model):
day = models.CharField(max_length=255, blank=True)
category = models.CharField(max_length=100, blank=True)
meal_time = models.TimeField(null=True, blank=True, verbose_name='Meal Time')
recipes = MultiSelectField(max_length=5000, choices=[], null=True)
meal_id = models.IntegerField(null=True, blank=True)
menu = models.ForeignKey(Menu, on_delete=models.CASCADE, null=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return "%s %s %s %s" % (
self.day, self.category, self.meal_time, self.recipes)
class Meta:
app_label = "mealmenumaster"
managed = True
class MealForm(forms.ModelForm):
day = DynamicChoiceField(choices=[], required=False)
category = forms.ChoiceField(choices=(('', 'None'),) + CATEGORY, required=False)
recipes = forms.MultipleChoiceField(label="Select Recipe(s)",
widget=forms.CheckboxSelectMultiple(), required=False)
meal_id = forms.CharField(widget=forms.HiddenInput(), required=False)
class Meta:
widgets = {'meal_time': TimeInput()}
model = Meal
fields = ['day', 'category', 'meal_time', 'recipes', 'meal_id']
app_label = "mealmenumaster"
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user_id', None)
super(MealForm, self).__init__(*args, **kwargs)
self.fields['recipes'].choices = [(x.name, x.name) for x in Recipe.objects.filter(user_id=user_id).order_by(Lower('name'))]
self.fields['meal_id'].initial = "0"
self.helper_meal = FormHelper()
self.helper_meal.form_tag = False
self.helper_meal.layout = Layout(
Row(
Div(
Field('day', css_class='form-control'),
css_class='form-group col-md-3 mb0'),
Div(
Field('category', css_class='form-control'),
css_class='form-group col-md-4 mb0'),
Div(
Field('meal_time', css_class='form-control'),
css_class='form-group col-md-4 mb0'),
css_class='form-group col-md-12 mb0'
),
Row(
Div(
Field('recipes', css_class="scrollbar-y"),
Field('meal_id', id='meal_id'),
css_class='form-group col-md-8 mb0'),
css_class='form-group row-md-10 mb0'),
)
And part of my View:
current_user = request.user
user_id = current_user.id
meal_form = MealForm(request.POST, user_id=request.user.id)
if meal_form.is_valid():
menu_id = request.POST.get('menu_id')
if menu_id == "":
messages.error(request, "Create and Save Menu Before Adding Meal(s)")
return redirect('menu')
else:
meal_id = meal_form.cleaned_data['meal_id']
category = meal_form.cleaned_data['category']
meal_time = meal_form.cleaned_data['meal_time']
recipes = meal_form.cleaned_data['recipes']
# get recipes for recipe id
if len(recipes) == 1:
recipes_list = recipes[0]
else:
recipes_list = ', '.join(recipes)
try:
existing_meal = Meal.objects.get(id=int(meal_id))
meal_form = MealForm(request.POST, instance=existing_meal)
# update meal object
saved_meal = meal_form.save(commit=False)
saved_meal.category = category
saved_meal.meal_time = meal_time
saved_meal.recipes = recipes_list
saved_meal.save()
messages.success(request, "Meal Updated")
return HttpResponseRedirect(reverse('menu', args=(),
kwargs={'menu_id': menu_id}))
except Meal.DoesNotExist:
# create new meal object
new_meal = meal_form.save(commit=False)
# set user foreign key
new_meal.user_id = user_id
new_meal.menu_id = int(menu_id)
# save meal
new_meal.save()
new_meal.meal_id = new_meal.id
new_meal.recipes = recipes_list
new_meal.save()
messages.success(request, "Meal Added")
return HttpResponseRedirect(reverse('menu', args=(),
kwargs={'menu_id': menu_id}))
else:
error = meal_form.errors.as_data()
message = "Form Error:", error
messages.error(request, message)
return render_forms(request)
I was able to resolve this issue by adding the actual choices to the model field. As shown below:
RECIPES = [(x.pk, x.name) for x in Recipe.objects.filter().order_by(Lower('name'))] added as choices in the models for the multi-select field "recipes"

MongoDB ObjectId "Instantiation" and saving

I use Django + MongoDB /Djongo for backend on Windows10/VSCode. How is it to instantiate document’s “ObjectId” like it is for other fields using Python? I have been struggling for a several days. Please help. Code example, below:
from djongo import models
class Blog(models.Model):
id= models.AutoField(
auto_created = True,
unique=True,
primary_key = True,
serialize = False,
verbose_name ='ID_nama: ')
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name, self.id
# return self.tagline
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField(default=date.today)
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField(default=0)
number_of_pingbacks = models.IntegerField(default=0)
rating = models.IntegerField(default=5)
def __str__(self):
return self.headline
Here is the document JSON from MongodDB:
{
“_id”: {
“$oid”: “626b6627f0d91c65e9f78cc6”
},
“id”: 5,
“name”: “Beatles Blog”,
“tagline”: “Beatles tour of the Americas.”
}
My target is to be able to capture the “ObjectId” => “_id”: {
“$oid”: “626b6627f0d91c65e9f78cc6”, and save it to another new field for other use/purpose.

how to post data in a nested serializer in Django rest framework

I am working on an app and need help I want to authenticate LinkedIn and save the access token in a table and then collect the LinkedIn user details in another table below is my model.py
class LinkedInUserCode(models.Model):
"""
Credentials for the user to give access to LinkedIn.
"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
token = models.CharField(max_length=1024)
expires_in = models.IntegerField()
def __str__(self):
return f'{self.token}'
class Meta:
db_table = 'linkedin_user_code'
verbose_name_plural = "LinkedIn User Codes"
class LinkedInProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
linkedin_id = models.CharField(max_length=1024, blank=True, null=True)
first_name = models.CharField(max_length=1024, blank=True, null=True)
last_name = models.CharField(max_length=1024, blank=True, null=True)
profile_image_url = models.CharField(max_length=1024, blank=True, null=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.linkedin_id}'
class Meta:
db_table = 'linkedin_profile'
verbose_name_plural = "LinkedIn Profiles"
I am using a nested serializer and this is my serialiser
class LinkedInProfileSerializer(ModelSerializer):
"""
Serializer for the LinkedInProfile model.
"""
id = IntegerField(required=False)
user = ReadOnlyField(source='user.email')
linkedin_access = ReadOnlyField(source='linkedin_access.token')
class Meta:
model = LinkedInProfile
fields = '__all__'
def create(self, validated_data):
"""
Create a new LinkedInProfile instance.
"""
return LinkedInProfile.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.linkedin_id = validated_data.get('linkedin_id')
instance.first_name = validated_data.get('first_name')
instance.last_name = validated_data.get('last_name')
instance.profile_image_url = validated_data.get('profile_image_url')
instance.linkedin_access = validated_data.get('linkedin_access')
instance.save()
return instance
class LinkedInUserCodeSerializer(ModelSerializer):
user = ReadOnlyField(source='user.email')
profiles = LinkedInProfileSerializer(many=True, read_only=True)
class Meta:
model = LinkedInUserCode
fields = ['user', 'token', 'expires_in', 'profiles']
def create(self, validated_data):
""" Create and return necessary credentials for linkedin."""
profile = validated_data.pop('profiles')
access_token = LinkedInUserCode.objects.create(**validated_data)
for profile_data in profile:
LinkedInProfile.objects.create(linkedin_access=access_token, **profile_data)
return access_token
def update(self, instance, validated_data):
profiles = validated_data.pop('profiles')
instance.token = validated_data.get('token', instance.token)
instance.expires_in = validated_data.get('expires_in', instance.expires_in)
instance.save()
temp_profile = []
existing_ids = [profiles.get('linkedin_id') for profile in instance.profiles.all()]
for profile_data in profile:
if profile_data.get('linkedin_id') in existing_ids:
temp_profile.append(LinkedInProfile.objects.get(linkedin_id=profile_data.get('linkedin_id')))
else:
temp_profile.append(LinkedInProfile.objects.create(linkedin_access=instance, **profile_data))
instance.profiles.set(temp_profile)
for profile in instance.profiles.all():
if profile not in temp_profile:
profile.delete()
return instance
I have written a script that I am using to make requests to the LinkedIn api and here is my view I want to populate it at once any idea how I can go about it I keep having issues with the foreign key of LinkedIn_access
class LinkedInCallbackAPIView(APIView):
"""
This View is used to get and save the Access Token from LinkedIn authorization page.
"""
def get(self, request, *args, **kwargs):
response = request.GET
if response is not None and state != response.get('state'):
return Response({'error': "Invalid state"}, status=status.HTTP_401_UNAUTHORIZED)
else:
queryset = LinkedInUserCode.objects.filter(user=request.user)
code = response.get('code')
access_token = linkedin.get_access_token(code)
profile = linkedin.get_user_profile(access_token.get('access_token'))
profile = {
'linkedin_id': profile.get('id'),
'first_name': profile.get('firstName').get('localized').get('en_US'),
'last_name': profile.get('lastName').get('localized').get('en_US'),
'profile_image_url': profile.get('profilePicture').get('displayImage'),
}
data = {
'token': access_token.get('access_token'),
'expires_in': access_token.get('expires_in'),
'user': request.user,
'profiles': profile,
}
serializer = LinkedInUserCodeSerializer(data=data)
if queryset.exists():
serializer.update(queryset.first(), data)
return Response({'message': 'Your LinkedIn user Access Token was Updated'},status=status.HTTP_200_OK)
if serializer.is_valid():
serializer.save(**data)
# there will be a return response here
I need help passing this table and not using a single table for it
thank you

How to overwrite data from a model that is linked to the main model through a o-t-m relationship?

I have three models that are related to each other, namely:
models.py
class Shop(models.Model):
number = models.PositiveSmallIntegerField()
name = models.CharField(db_index=True)
city = models.ForeignKey(ShopCity, on_delete=models.CASCADE)
class Product(models.Model):
name = models.CharField(db_index=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
class ProductQuantity(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
shop = models.ForeignKey(Shop, on_delete=models.CASCADE)
quantity = models.PositiveSmallIntegerField(default=None)
In the admin panel they are linked in this way:
admin.py
class ProductQuantityInline(admin.TabularInline):
model = ProductQuantity
extra = 0
#admin.register(Product)
class ProductAdmin(ImportExportActionModelAdmin):
fields = ['name', 'price']
list_display = ['name', 'price']
inlines = [ProductQuantityInline]
There is a need to overwrite data with REST API
serializers.py
class QuantitySerializer(serializers.ModelSerializer):
class Meta:
model = ProductQuantity
fields = ('shop', 'quantity')
class ProductSerializer(serializers.ModelSerializer):
productquantity = serializers.SerializerMethodField(read_only=False)
class Meta:
model = Product
fields = ('name', 'price', 'productquantity')
def get_productquantity(self, obj):
return [QuantitySerializer(s).data for s in obj.productquantity_set.all()]
And finally my handler for REST API:
views.py
#api_view(['GET', 'PATCH', 'PUT', 'DELETE'])
def api_product_detail(request, pk):
product = Product.objects.get(pk=pk)
if request.method == 'GET':
serializer = ProductSerializer(product)
return Response(serializer.data)
elif request.method == 'PUT' or request.method == 'PATCH':
serializer = ProductSerializer(product, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
product.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
As a result, data such as the name and price are overwritten, and the productquantity is not overwritten.
What am I doing wrong? Thanks for any help.

Django AttributeError 'Tag' object has no attribute 'summary'

I run into this error whenever I tried to add a new Tag. I tried to google and read some SO posts, but nothing work for me.
Here are my 2 models :
Summary :
class Summary(models.Model):
question_text = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
url = models.URLField(null=False)
cover_image = models.CharField(max_length=255)
tags = models.ManyToManyField('Tag', related_name='summaries', blank=True)
userProfileSummary = models.ManyToManyField('UserProfile', through='UserProfileSummary')
def __str__(self):
return self.question_text
class Meta:
verbose_name_plural = "Summaries"
Tag :
class Tag(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
models/init.py:
...
from .tag import Tag
from .summary import Summary
__all__ = (
...
'Tag',
'Summary',
)
Error :
Please, what did I miss ?

Resources