How do I add together fields from a manytomanyfield in django? - python

I am creating a quote-generator in Django. I want to calculate the total of all items, insert it into a field, and save it.
The models are as follows:
from django.db import models
from django.core.urlresolvers import reverse
class Product(models.Model):
product_name = models.CharField(max_length=50)
product_description = models.CharField(max_length=200)
product_price = models.IntegerField(max_length=4)
def __unicode__(self):
return self.product_name
class Meta:
ordering = ('product_name',)
class Quote(models.Model):
quotee_name = models.CharField("Name", max_length=40)
quotee_email = models.EmailField("Email")
quotee_phone = models.IntegerField("Phone", max_length=10)
quotee_products = models.ManyToManyField(Product, verbose_name="Products")
quotee_total = models.IntegerField("Estimate", max_length=10, null=True, blank=True)
def __unicode__(self):
return self.quotee_email
class Meta:
ordering = ('quotee_email',)
def get_absolute_url(self):
return reverse('quote-detail', kwargs={'pk': self.pk, })
I am not using this through the Admin, so here is the forms.py:
from django import forms
from django.forms import CheckboxSelectMultiple
from InternalDusettenet.apps.quotes.models import Quote
class QuoteForm(forms.ModelForm):
class Meta:
model = Quote
fields = ('quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products')
widgets = {
'quotee_products': CheckboxSelectMultiple(attrs={'size': 10}),
}
And here is the views.py file. I have it set to just save a '1' into the form so that it actually saves. What I want is to replace the '1' with a function that returns the value of 'Product.product_price' for every one selected in 'Quote.quotee_products'. When I create a quote, I select the products, and it gives me the sum of all selected 'product_price' fields related to the selected products:
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView, DetailView
from django.shortcuts import Http404, get_object_or_404
from InternalDusettenet.apps.quotes.models import Quote
from InternalDusettenet.apps.quotes.forms import QuoteForm
class QuoteCreate(CreateView):
model = Quote
template_name = "quotes/quote_create_edit.html"
fields = ['quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products']
form_class = QuoteForm
def form_valid(self, form):
form.instance.quotee_total = 1
return super(QuoteCreate, self).form_valid(form)
class QuoteList(ListView):
model = Quote
template_name = "quotes/quote_list.html"
class QuoteDetail(DetailView):
model = Quote
template_name = "quotes/quote_detail.html"
class QuoteUpdate(UpdateView):
model = Quote
template_name = "quotes/quote_create_edit.html"
fields = ['quotee_name', 'quotee_email', 'quotee_phone',
'quotee_products', 'quotee_total']
form_class = QuoteForm
class QuoteDelete(DeleteView):
model = Quote
success_url = '/'
template_name = "quotes/quote_delete.html"
I have read the Django docs MANY times but I have no clue how to do this one simple thing.
I am using Django 1.7 and Python 2.7.

No reason to save it in the database, just make it a method or property of the Quote object:
class Quote(models.Model):
...
def quotee_total(self):
return self.quotee_products.aggregate(total=models.Sum('product_price'))['total']
If need be, you can cache the value and fill the cache on the initial query:
class Quote(models.Model):
...
def quotee_total(self):
if not hasattr(self, '_quotee_total'):
self._quotee_total = self.quotee_products.aggregate(total=models.Sum('product_price'))['total']
return self._quotee_total
quotes = Quote.objects.annotate(_quotee_total=models.Sum('quotee_products__product_price'))
You can of course save that value in the database, but there's little reason. If you're worried about performance, that is better handled with caching than with saving the value to the database.

I would not calculate the total in a view. This makes more sense as a method.
class Quote(models.Model):
def calculate_quotee_total(self):
return sum(product.product_price for product in self.quotee_products.all())
def __save__(self):
self.quotee_total = self.calculate_quotee_total()
super(Quote, self).save()
Quote.quotee_total could also be calculated as needed, instead of saving it in the db.
class Quote(models.Model):
#property
def quotee_total(self):
return sum(product.product_price for product in self.quotee_products.all())

Related

Form UpdateView and DeleteView and 3 hierarchical models

I am trying to create an 'Expense Tracker'. I have query regarding UpdateView and DeleteView and Models with 3 hierarchical levels. I was able to create the CreateView for the 'Expense' model, but the update and delete view are throwing a lot of errors. Can you please tell how to write the code for update and deleteview (for 'Expense' model).
Models.py
class Year(models.Model):
year = models.IntegerField(choices=year_choices()
,validators=[MinValueValidator(1984),
max_value_current_year], name='year',
unique = True)
def __str__(self):
return str(self.year)
class Meta():
ordering = ('-year','-year')
class Month(models.Model):
month = models.CharField(choices=month_choices(), max_length=264)
month_year = models.ForeignKey(Year,related_name = 'month',on_delete=models.CASCADE)
def __str__(self):
return str(self.month) + ' ' + str(self.month_year)
def get_absolute_url(self):
return reverse("Expense_App:Year")
class Meta():
unique_together = [['month','month_year']]
class Expense(models.Model):
Home_Expense = models.PositiveIntegerField()
Groceries = models.PositiveIntegerField()
Electronics = models.PositiveIntegerField()
Snacks = models.PositiveIntegerField()
Other = models.PositiveIntegerField()
total = models.PositiveIntegerField()
expense_month = models.ForeignKey(Month,related_name = 'expense', on_delete=models.CASCADE)
def __str__(self):
return str(self.expense_month) + ' ' + str(self.total)
def get_absolute_url(self):
return reverse("Expense_App:Year")
Forms.py
#forms.py
class ExpensesForm(forms.ModelForm):
class Meta():
model = Expenses
fields = ('expenses','expense_month')
def __init__(self, *args, **kwargs):
month_year_id = kwargs.pop('month_year_id')
super(ExpensesForm, self).__init__(*args, **kwargs)
self.fields['expense_month'].queryset = ExpenseMonth.objects.filter(month_year_id=month_year_id)
Views.py
#createview for 'expense' model
class ExpenseCreateView(CreateView):
model = models.Expense
form_class = ExpenseForm
def get_form_kwargs(self):
kwargs = super(ExpenseCreateView, self).get_form_kwargs()
kwargs.update({'month_year_id': self.kwargs.get('pk')})
return kwargs
Urls.py
Update and Delete View will be in these urls (when they are created)
#Update and delete views' url
path('year/<int:pk>/<int:pk2>/update_expense/',views.ExpenseUpdateView.as_view(),name = 'update_expense'),
path('year/<int:pk>/<int:pk2>/delete_expense/',views.ExpenseDeleteView.as_view(),name = 'delete_expense'),
i checked Django Documentation, but i could find any particular answer for this. Can please Help me out. I searched many questions related to this in stackoverflow and other websites,but couldn't find the answer. Can you please answer this question in this context itself (pls don't reply with a genenral code as, i am new to django and find it difficult to understand general answers)
Thanks in Advance !
The problems are:
django takes pk instead pk2 at some places where pk2 need to be used.
sometimes updateview and deleteview doesn't even show up, instead raise a page not found error (Desptite correct views and url configuration).
Update View Doesn't update the 'expense' model, instead creates a new one.
It's behaving very weird. Sometimes Working and sometimes not working.
#UpdateView
class ExpenseUpdateView(UpdateView):
model = models.Expense
form_class = ExpenseForm
def get_form_kwargs(self):
kwargs = super(ExpenseUpdateView, self).get_form_kwargs()
kwargs.update({'month_year_id': self.kwargs.get('pk')})
return kwargs
#DeleteView
class ExpenseDeleteView(DeleteView):
model = models.Expense
success_url = reverse_lazy('Expense_App:Year')

Getting an empty list when using filter - Django REST

I have my API in Django REST Framework:
Here is my models.py:
class myModel(models.Model):
user_email = models.CharField(max_length= 200, null= False)
Here is my views.py:
class GetItemsByEmail(generics.ListAPIView):
def get_queryset(self):
email_items = self.request.query_params.get("user_email")
if(email_items is not None):
itemsReturned = myModel.objects.all().filter(user_email = email_items)
return Response(data= itemsReturned)
Here is my urls.py:
url_patterns = [
path('users/account=<str:id>/shipments', GetItemsByEmail.as_view()),
]
My Question:
I am getting an empty list, getting nothing from making an API call to the above endpoint.
I want to get all the items in the database associated with a particular email?
In your views.py:
from rest_framework import generics
from .models import * # noqa
from .serializers import *
class GetItemsByEmail(generics.ListAPIView):
queryset = MyModel.objects.all() # noqa
serializer_class = MyModelSerializer
def get_queryset(self):
if self.kwargs.get('user_email_pk'):
return self.queryset.filter(id=self.kwargs.get('user_email_pk'))
return self.queryset.all()
In models.py I had to create another model to have the result that you want (get all database by a specific user_email!):
from django.db import models
class MyModel(models.Model):
user_email = models.CharField(max_length=200, null=False)
def __str__(self):
return self.user_email
class ServicesModel(models.Model):
# Just an example to emulate the expected result, do not worry about it!
name = models.CharField('Name', max_length=200)
user_email_service = models.ForeignKey(MyModel, related_name='services', on_delete=models.CASCADE) # Just an example to emulate the expected result, do not worry about it!
def __str__(self):
return self.name
In serializers.py:
from rest_framework import serializers
from .models import MyModel, ServicesModel
class ServiceModelSerializer(serializers.ModelSerializer):
class Meta:
model = ServicesModel
fields = (
'name',
)
class MyModelSerializer(serializers.ModelSerializer):
services = ServiceModelSerializer(many=True, read_only=True)
class Meta:
model = MyModel
fields = (
'id',
'user_email',
'services',
)
In urls.py:
from django.urls import path
from core.views import GetItemsByEmail
urlpatterns = [
path('users/', GetItemsByEmail.as_view(), name='users'), # Ignore!
path('users/account=<str:user_email_pk>/shipments/', GetItemsByEmail.as_view(), name='user_email'),
]
In the test that I made localy I created two 'user_email' and each one have diferent 'services' so you are able to get all the data by the id, images of the result:
You obviously only need to get attention in 'views.py' and 'serializers.py', I just created all this code to get in the expected result!
If you want your query to be case insensitive, you can try the following:
myModel.objects.filter(user_email__iexact=email_items)

How can I filter objects in my model to only show those of my ForeignKey user?

Trying to filter objects in my view.py to only show items (in my case Buckets) owned by Users.
I implemented the code below in my original Model [my model.py code is at the bottom of post]
class PostObjects(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status=Bucket.owner)
But I'm not sure if that is the correct procedure to list all items?
Here is the view.py where I'm trying to filter data by User aka owner. Users should ONLY be allowed to view their own items. (I will deal with permissions later)
class BucketList(generics.ListCreateAPIView):
queryset = Bucket.objects.all() #INSERT FILTER HERE
pass
Here is the model I'm referring too.
class Bucket(models.Model):
options = (
('personal', 'Personal'),
('social', 'Social'),
)
class PostObjects(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status=Bucket.owner)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='buckets')
users = models.ManyToManyField(settings.AUTH_USER_MODEL)
category = models.CharField(max_length=30, choices=options)
name = models.CharField(max_length=35)
created = models.DateTimeField(default=timezone.now)
slug = models.SlugField(unique=True, blank=True)
stock_count = models.IntegerField(blank=True, null=True)
stock_list = ArrayField(models.CharField(max_length=6),size=10)
objects = models.Manager()
postobjects = PostObjects()
class Meta:
ordering = ('-created',)
def total_stocks_calc(self):
self.stock_count = Bucket.objects.aggregate(Sum('stock_list', distinct=True))
self.save()
def get_absolute_url(self):
return reverse("bucket:bucket-view", kwargs={"slug": self.slug})
def __str__(self):
return self.stock_list
To re-state my question, how can I filter objects owned by users in class BucketList for their private view only?
UPDATE:
from django.db.models import Q
from rest_framework import generics
from bucket.models import Bucket
from .serializers import BucketSerializer
class OwnerOrUserFilterBackend(filters.BaseFilterBackend):
queryset = Bucket.postobjects.all() # wondering if you didnt write this for brevity reasons or because its not need due to the class?
def filter_queryset(self, request, queryset, view):
return queryset.filter(
Q(owner=request.user) | #do I not need to change one of the filters too request.owner?
Q(users=request.user)
)
class BucketList(generics.ListCreateAPIView):
model = Bucket
filter_backends = [OwnerOrUserFilterBackend]
You can override the get_queryset method and filter with self.request.user:
class BucketList(generics.ListCreateAPIView):
model = Bucket
def get_queryset(self, *args, **kwargs):
return super.get_queryset(*args, **kwargs).filter(
owner=self.request.user
)
For an API view, it probably is however better to define a filter, and then use this over all the API views where you want to apply this:
class IsOwnerFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(owner=request.user)
Then you can use this in your ListCreateAPIView with:
class BucketList(generics.ListCreateAPIView):
model = Bucket
filter_backends = [IsOwnerFilterBackend]
The advantage of this is that if you have other views that require the same filtering, you only need to add the IsOwnerFilterBackend as filter_backend.
Another filter could include both the owner and the users as people who can see the BucketList:
from django.db.models import Q
class OwnerOrUserFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(
Q(owner=request.user) |
Q(users=request.user)
)
then we thus filter with this filter:
class BucketList(generics.ListCreateAPIView):
model = Bucket
filter_backends = [OwnerOrUserFilterBackend]

Python DRF PrimaryKeyRelatedField to use uuid instead of PK

I'm writing a Django REST Framework API.
My models have default Django PK for internal use AND uuid field for external reference.
class BaseModel(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
class Event(BaseModel):
title = models.TextField()
location = models.ForeignKey('Location', null=True, on_delete=models.SET_NULL)
class Location(BaseModel):
latitude = models.FloatField()
longitude = models.FloatField()
And my serializers:
class BaseSerializer(serializers.ModelSerializer):
default_fields = ('uuid',)
class EventSerializer(BaseSerializer):
class Meta:
model = Event
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('title', 'location',)
class LocationSerializer(BaseSerializer):
class Meta:
model = Location
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('latitude', 'longitude',)
This works fine, here is what I got when I retrieve an Event:
{
"uuid": "ef33db27-e98b-4c26-8817-9784dfd546c6",
"title": "UCI Worldcup #1 Salzburg",
"location": 1 # Note here I have the PK, not UUID
}
But what I would like is:
{
"uuid": "ef33db27-e98b-4c26-8817-9784dfd546c6",
"title": "UCI Worldcup #1 Salzburg",
"location": "2454abe7-7cde-4bcb-bf6d-aaff91c107bf" # I want UUID here
}
And of course I want this behavior to work for all my ForeignKeys and ManyToMany fields.
Is there a way to customize the field used by DRF for nested models ?
Thanks !
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.relations import RelatedField
from django.utils.encoding import smart_text
class UUIDRelatedField(RelatedField):
"""
A read-write field that represents the target of the relationship
by a unique 'slug' attribute.
"""
default_error_messages = {
'does_not_exist': _('Object with {uuid_field}={value} does not exist.'),
'invalid': _('Invalid value.'),
}
def __init__(self, uuid_field=None, **kwargs):
assert uuid_field is not None, 'The `uuid_field` argument is required.'
self.uuid_field = uuid_field
super().__init__(**kwargs)
def to_internal_value(self, data):
try:
return self.get_queryset().get(**{self.uuid_field: data})
except ObjectDoesNotExist:
self.fail('does_not_exist', uuid_field=self.uuid_field, value=smart_text(data))
except (TypeError, ValueError):
self.fail('invalid')
def to_representation(self, obj):
return getattr(obj, self.uuid_field)
Sample Usage:
class ProductSerializer(serializers.ModelSerializer):
category = UUIDRelatedField(
queryset=Category.objects.all(),
uuid_field='alias'
)
class Meta:
model = Product
fields = (
'id',
'alias',
'name',
'category',
)
read_only_fields = (
'id',
'alias',
)
Note that as of Django version 4, smart_text and ugettext_lazy were removed, use smart_str and gettext_lazy instead of them:
from django.utils.encoding import gettext_lazy
from django.utils.encoding import smart_str
A friend of mine send me this solution:
It works with all my related objects.
from rest_framework import serializers
from rest_framework.relations import SlugRelatedField
class UuidRelatedField(SlugRelatedField):
def __init__(self, slug_field=None, **kwargs):
slug_field = 'uuid'
super().__init__(slug_field, **kwargs)
class BaseSerializer(serializers.ModelSerializer):
default_fields = ('uuid',)
serializer_related_field = UuidRelatedField
class Meta:
pass
For nested model fields you can use the source argument in a serializer like this
class EventSerializer(BaseSerializer):
location = serializers.CharField(source='location.uuid')
class Meta:
model = Event
lookup_field = 'uuid' # This does not work
fields = BaseSerializer.default_fields + ('title', 'location',)

Django models class attribute and instance property?

I implement very simple hit-count models in Django.
models.py
from django.db import models
from model_utils.models import TimeStampedModel
from posts.models import Post
class PostHit(TimeStampedModel):
post = models.ForeignKey(Post, related_name='post_hits')
num_of_hit = models.IntegerField()
class Meta:
verbose_name_plural = "Post hits"
def __str__(self):
return self.post.title
def increase_hit(self):
self.num_of_hit += 1
views.py
from django.views.generic.detail import DetailView
from django.core.exceptions import ObjectDoesNotExist
from posts.models import Post, PostHit
from posts.forms import CommentForm
class PostDetailView(DetailView):
model = Post
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
context['category'] = self.kwargs['category']
context['form'] = CommentForm()
context['tracking_hit_post'] = self.tracking_hit_post()
return context
def tracking_hit_post(self):
post = self.model.objects.get(pk=self.object.id)
post_hit = PostHit.objects.filter(post=post).first()
if post_hit:
post_hit.increase_hit()
else:
post_hit = PostHit.objects.create(
post=post,
num_of_hit=1
)
print(post_hit.num_of_hit)
return post_hit.num_of_hit
Once PostHit instance created, it calls increase_hit() everytime I visit DetailVie.
But it doesn't increase right way.
First it prints 1. And when I refresh the page, it prints 2. At next refresh, it prints 2 again. It doesn't increase anymore after 2.
What's wrong with my code? Did I misunderstand class attribute and instance property?
You need to save the model after updating it:
def increase_hit(self):
self.num_of_hit += 1
self.save()
Otherwise, your changes persist only for the lifetime of the object.

Categories