I had no idea adding data to a queryset would be so hard. It's like, if it didn't come directly from the db then it might as well not exist. Even when I annotate, the new fields are 2nd class citizens and aren't always available.
Why won't serialize capture my annotate fields?
Model
class Parc(models.Model):
# Regular Django fields corresponding to the attributes in the
# world borders shapefile.
prop_id = models.IntegerField(unique=True) # OBJECTID: Integer (10.0)
shp_id = models.IntegerField()
# GeoDjango-specific: a geometry field (MultiPolygonField)
mpoly = models.MultiPolygonField(srid=2277)
sale_price = models.DecimalField(max_digits=8, decimal_places=2, null=True)
floorplan_area = models.DecimalField(max_digits=8, decimal_places=2, null=True)
price_per_area = models.DecimalField(max_digits=8, decimal_places=2, null=True)
nbhd = models.CharField(max_length=200, null=True)
# Returns the string representation of the model.
def __str__(self): # __unicode__ on Python 2
return str(self.shp_id)
Query:
parcels = Parc.objects\
.filter(prop_id__in=attrList)\
.order_by('prop_id') \
.annotate(avg_price=Avg('sale_price'),
perc_90_price=RawAnnotation('percentile_disc(%s) WITHIN GROUP (ORDER BY sale_price)', (0.9,)),
)
geojson = serialize('geojson', parcels)
When I print geojson it has no key/values for avg_price or perc_90_price. At this point, I'm leaning towards creating a dummy field and then populating it with the my customer calculations after I retrieve the queryset but I'm open to ideas.
Helper class
class RawAnnotation(RawSQL):
"""
RawSQL also aggregates the SQL to the `group by` clause which defeats the purpose of adding it to an Annotation.
"""
def get_group_by_cols(self):
return []
I use annotations with Django Rest Framework and the Serializers in that library.
In particular, the serializer method allows you to access the query set. You can do something like this.
class SomeSerializer(serializers.ModelSerializer):
avg_price = serializers.SerializerMethodField()
def get_avg_price(self, obj):
try:
return obj.avg_price
except:
return None
As mentioned by Carl Kroeger Ihl, you can also use:
class SomeSerializer(serializers.ModelSerializer):
avg_price = serializers.IntegerField(allow_null=True)
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
Related
I have a database representing financial transactions. Columns representing payee and category are non-optional.
However, part of my app's functionality will be to ingest external spreadsheets of transactions which do not already have payee and category information. I would then populate a form where the user will select correct payees and categories through drop-down menus, and then save the completed information to the database.
Is the correct approach to simply create two separate but equivalent classes (see below)? Or is there some way to make one a sub-class to another, despite the fact that one is connected to a database and the other is not.
# An initial class representing a transaction read from an Excel sheet
# Payee and category information are missing at this stage, but will be filled
# in by the user later on
class TransactionFromSpreadsheet:
def __init__(self, date, amount):
self.date = date
self.amount = amount
self.payee = None
self.category = None
# The Django class that will be instantiated once all the user has completed all
# necessary information
class Transaction(models.Model):
date = models.DateField()
amount = models.DecimalField(max_digits=14, decimal_places=2)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
payee = models.ForeignKey('Payee', on_delete=models.CASCADE)
One could use optional foreign keys and a custom manager to provide an easy way to query the "incomplete" or "complete" transactions.
class TransactionQueryset(models.query.QuerySet):
def complete(self):
return self.filter(category__isnull=False,
payee__isnull=False)
def incomplete(self):
return self.filter(category__isnull=True,
payee__isnull=True)
class TransactionManager(models.Manager):
def get_queryset(self):
return TransactionQueryset(self.model, using=self._db)
def complete(self):
return self.get_queryset().complete()
def incomplete(self):
return self.get_queryset().incomplete()
class Transaction(models.Model):
date = models.DateField()
amount = models.DecimalField(max_digits=14, decimal_places=2)
category = models.ForeignKey('Category', on_delete=models.CASCADE,
blank=True, null=True)
payee = models.ForeignKey('Payee', on_delete=models.CASCADE,
blank=True, null=True)
objects = TransactionManager()
And if you now need an incomplete transaction you could easily get these in a view:
def view_incomplete(request):
incompletes = Transaction.objects.incomplete()
return render(request, 'incomplete_template.html',
{'incompletes': incompletes})
It is now very comfortable to gather all heavily used filter conditions in the queryset and manager class.
And if you have non complementary filter conditions you could even chain the manager functions.
# Here is my models
This is my CustmerBuySell model DB designed.
class CustomerBuySell(models.Model):
customer = models.ForeignKey(CustomerAdd, on_delete=models.CASCADE)
customer_buy_sell_debit = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
customer_buy_sell_credit = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
description = models.CharField(max_length=250, blank=True)
date = models.DateField()
sms = models.BooleanField(default=False)
picture = models.ImageField(upload_to='customer_buy_sell_pics', default='images.png')
created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True)
updated_at = models.DateTimeField(auto_now=True, blank=True, null=True)
def __str__(self):
return self.customer.customer_name
class Meta:
verbose_name = "Customer BuySell"
verbose_name_plural = "Customer BuySell"
# Here, is my View.
This is the class-based APIView, which I have used. And try to use the aggregate query in this view.
class DailyCustomerBuySellAPIView(APIView):
def get(self, request):
customer_buy_sell = CustomerBuySell.objects.extra(select={'day': 'date( date )'}).values('day').order_by(
'date__date').annotate(available=Count('date__date'))
serializer = CustomerBuySellSerializer(customer_buy_sell, many=True)
return Response({"customer_buy_sell": serializer.data})
# And, finally here are my Serializers
I have no idea what's the problem! Please help me.
class CustomerBuySellSerializer(serializers.ModelSerializer):
# customer = CustomerAddSerializer()
class Meta:
model = CustomerBuySell
fields = '__all__'
def to_representation(self, instance):
representation = super(CustomerBuySellSerializer, self).to_representation(instance)
if instance.customer is not None:
customer_name = instance.customer.customer_name
previous_due = instance.customer.previous_due
representation['custo`enter code here`mer_name'] = customer_name
representation['previous_due'] = previous_due
return representation
There are many problems with your approach. Let me mention each of them one by one:
First of all remove date__date from your APIVIew
Before:
customer_buy_sell = CustomerBuySell.objects.extra(select={'day': 'date( date )'}).values('day').order_by(
'date__date').annotate(available=Count('date__date'))
Instead, write it as:
from django.db.models.functions import Extract
customer_buy_sell = CustomerBuySell.objects.annotate(day=Extract('date','day')).values('day').order_by('day')
if you need a count of the days then you can try
customer_buy_sell_count = customer_buy_sell.count()
Another thing that you are doing wrong is you pass a dict to serializer as you are already using values that return a dictionary of days and not object of CustomerBuySell so you do not need to pass it to serializer otherwise you have to do it according to your need.
In CustomerBuySellSerializer you are using a model serializer with __all__ while you are passing an extra fields day that is not part of it.
So in short there are so many syntax issues with your Django and Django Rest Framework.Great way to fix such issues is to set with an experience programmer so that he can improve the flow of the code. Later on you can focus on logic.
I suppose it is just a typo: Change date__date to date
query = Services.objects.filter(parent_id__isnull=True,sub_service__type=0)
When im filtering sub_service__type=1 It returns correct output. i.e. type 1 for sub_service, But when i change it to sub_service__type=0 filters doesnot work. Instead it returns me every output. i.e. type 0,1 Instead of type 0
Heres Code:
# MODELS
class Services(models.Model):
type_ = ((1, 'INCLUSIVE'),
(0, 'EXCLUSIVE'))
service_id = models.AutoField(primary_key=True)
parent_id = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='sub_service')
type = models.SmallIntegerField(blank=True, null=True, choices=type_)
# VIEWS
#action(detail=False, methods=['get'], permission_classes=(AllowAny,))
def service_list(self, request):
query = Services.objects.filter(parent_id__isnull=True,sub_service__type=0)
serializer = ServiceSerializer(query , many=True).data
return Response(serializer)
# SERIALIZER
class SubServiceSerializer(serializers.ModelSerializer):
class Meta:
model = Services
fields = "__all__"
class ServiceSerializer(serializers.ModelSerializer):
sub_service = SubServiceSerializer(many=True)
class Meta:
model = Services
fields = "__all__"
If you filter with sub_service__type=1 you retrieve all Services that have at least one related Service with type=1. But it is thus allowed that there are other related sub-Services with a different type. The .sub_service manager will furthermore not filter the queryset of related objects.
You can make use of a Prefetch object [Django-doc] to filter the relation as well:
from django.db.models import Prefetch
query = Services.objects.filter(
parent_id__isnull=True,sub_service__type=0
).prefetch_related(Prefetch('sub_service', Service.objects.filter(type=0)))
The queryset for the 'jurisdiction' field is set below in the initialization. The queryset is dependent on the id that is passed in, which comes from a specific link that a user clicks. As a result, I can't define a singular queryset within the forms.ModelChoiceField(), but it seems that django requires me to do this.
class TaxForm (forms.ModelForm): #Will be used for state tax and other taxes
jurisdiction = forms.ModelChoiceField(queryset=?????)
class Meta:
model = Tax
exclude = ('user', 'taxtype',)
def __init__(self, *args, **kwargs):
self.taxtype = kwargs.pop('taxtype',None)
super(TaxForm, self).__init__(*args, **kwargs)
if int(self.taxtype) == 1:
self.fields['jurisdiction'].choices = [(t.id, t) for t in State.objects.all()]
elif int(self.taxtype) == 2:
self.fields['jurisdiction'].choices = [(t.id, t) for t in Country.objects.all()]
else:
self.fields['jurisdiction'].choices = [(t.id, t) for t in State.objects.none()]
How can I indicate that I want the jurisdiction field to be a dropdown, but not specify one queryset within the forms.ModelChoiceField()? Alternatively, how can I make the queryset that is referenced in forms.ModelChoiceField() refer to the queryset that I initialize based on the taxtype?
Thanks!
Here is my tax model
class Tax(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
jurisdiction = models.CharField(max_length=120, null=True, blank=True)
name = models.CharField(max_length=200)
rate = models.DecimalField(max_digits=10, decimal_places=2)
basis = models.CharField(max_length=120, null=True, blank=True)
regnumber = models.CharField(max_length=200, null=True, blank=True) #tax number that will appear on customer invoice
taxtype = models.IntegerField(blank=True, null=True) # 0 is other, 1 is state, 2 is federal
def __str__(self):
return '{} - {}'.format(self.user, self.name)
As I mentioned, ModelChoiceField is not the right thing to do here. That's for allowing the user to choose from related items from a single model that will be saved into a ForeignKey. You don't have a ForeignKey, and what's more you're setting the choices attribute in your init rather than queryset. You should make it a plain ChoiceField with an empty choices parameter:
jurisdiction = forms.ChoiceField(choices=())
(For the sake of completeness: if you did need to use ModelChoiceField you can put anything you like into the queryset parameter when you're overwriting it in __init__, because it will never be evaluated. But managers have a none method which returns an empty queryset, so you could do queryset=State.objects.none().)
I'm currently having some difficulty implementing dynamic choice for a field in my model. The model in question is as below.
class Item(models.Model):
# Fields
...
item_type = models.ForeignKey(Type, on_delete=models.PROTECT)
Type model
class Type(models.Model):
# Fields
group = models.CharField(max_length=50, editable=False)
name = models.CharField(max_length=30, help_text="Enter category name")
...
# Methods
def __str__(self):
return str(self.name)
What I want is to limit the number of Type returned for this field. Each of my Type entry has a 'group' field, and I only want to return the Type entry that has the same group as the group that the current user is in. For example, an user in Group "g1" when create an Item could only select Type with "g1" in group field.
I've checked out limit_choices_to, but I'm not sure how I can get the group of the user opening the form so that I can pass it into limit_choices_to. If anyone can provide an advice or any different approach to solve this it would be appreciated.
You can override default objects manager or add custom one. In your case would be something like this.
class TypesManager(models.Manager):
def get_queryset(self, user_group):
qs = super(TypesManager,self).get_queryset().filter(group_id=user_group.id)
class Type(models.Model):
group = models.ForeignKey(Group, verbose_name='Group')
name = models.CharField(max_length=30, help_text="Enter category name")
# default_manager
objects = TypesManager()
# custom_manager
custom_manager = TypesManager()
# Methods
def __str__(self):
return str(self.name)'
You can read more about Managers here https://docs.djangoproject.com/en/2.0/topics/db/managers/
Or if you want to limit types only for specific form you can filter initial data in form init