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.
Related
I have two tables Subjectlist and Day. Subject list is m2m in Day. So my problem is I'm creating school timetable. So for each days different subjects to be shown, when i add subjects on each days the order of subject is same.
#Models.py
class SubjectList(models.Model):
subject_name = models.CharField(max_length=25)
def __str__(self):
return self.subject_name
class Day(models.Model):
day_name = models.CharField(max_length=15)
subject_name = models.ManyToManyField(SubjectList)
class_number = models.ForeignKey(AddClass, on_delete=models.CASCADE, null=True, blank=True)
start_time = models.TimeField(null=True, blank=True)
end_time = models.TimeField(null=True, blank=True)
def __str__(self):
return self.class_number.class_number
#Views.py
class TimeTableView(APIView):
def get(self, request, id):
class_number = AddClass.objects.get(id=id)
day = Day.objects.filter(class_number=class_number.id)
print(day)
serializer = DaySerializer(day, many=True)
return Response(serializer.data)
I want to do like this
Monday - English, maths, science, Social Science
Tuesady - Maths, Social Science, Englih, Math's
but i get like this
Monday - English, maths, science, Social Science
Tuesday- English, maths, science, Social Science
both are in same order even if add subjects in different order.
You can add more fields to the M2M table by declaring a model and assigning it to the M2M relation with the through parameter
class DaySubjectList(models.Model):
day = models.ForeignKey(Day, on_delete=models.CASCADE, related_name="day_subject_lists")
subject_list = models.ForeignKey(SubjectList, on_delete=models.CASCADE, related_name="day_subject_lists")
order = models.IntegerField(default=0)
class Day(models.Model):
day_name = models.CharField(max_length=15)
subject_name = models.ManyToManyField(SubjectList, through=DaySubjectList)
class_number = models.ForeignKey(AddClass, on_delete=models.CASCADE, null=True, blank=True)
start_time = models.TimeField(null=True, blank=True)
end_time = models.TimeField(null=True, blank=True)
def __str__(self):
return self.class_number.class_number
You can set the order field using the through_defaults parameter of the related manager
my_day.subject_name.add(my_subject, through_defaults={"position": my_position})
You can order using related_name
Day.objects.filter(class_number=class_number.id).prefetch_related(Prefetch("day_subject_lists", queryset=DaySubjectList.objects.order_by("position")))
Records will only be ordered if you access my_day.day_subject_lists.all() not my_day.subject_name.all()
You can store those records under another name if thats more convenient
Day.objects.filter(class_number=class_number.id).prefetch_related(Prefetch("day_subject_lists", queryset=DaySubjectList.objects.order_by("position"), to_attr="my_prefered_name"))
be aware that my_day.my_prefered_name is a list not a queryet so don't use .all() to access it.
Use you have any issue with your models referencing each other you can use string synthax
class DaySubjectList(models.Model):
day = models.ForeignKey("myapp.Day", on_delete=models.CASCADE, related_name="day_subject_lists")
subject_list = models.ForeignKey("myapp.SubjectList", on_delete=models.CASCADE, related_name="day_subject_lists")
order = models.IntegerField(default=0)
You need to specify an order for the m2m relation [Django-doc]:
class Day(models.Model):
# ...
subject_name = models.ManyToManyField(
SubjectList,
ordered=True
)
# ...
Now if you query a Day object, the related SubjectList objects will be ordered in the order you added these.
You can further alter the order with .move(...), or .reorder(...).
Note: You should rename the subject_name field to subjects, since it relates to multiple SubjectList objects. A ForeignKey or OneToOneField indeed often uses the _name suffix, since these relate to at most one other object, but a ManyToManyField does not have this limitation.
Note: You should not use ForeignKey(.., null=True)s. A foreign key represents a relation, and a relation can not be "NULL". You should make these fields not nullable, and make the related model have a nullable parent relation (with null=True), or make the field optional with blank=True.
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 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
I've read the documentation but am still coming up with errors. I have Users placing Orders for Catalog objects. I'd like to create a query which returns all Users that have an Order containing a specific Catalog item.
Here are my models:
class Catalog(models.Model):
name = models.CharField(max_length=100)
price = models.IntegerField()
def __unicode__(self):
return self.name
class Annual(models.Model):
catalog = models.OneToOneField(Catalog, blank=True, null=True, related_name='annual_products')
year_id = models.IntegerField(max_length=4)
start_date = models.CharField(max_length=10)
end_date = models.CharField(max_length=10)
date = models.DateTimeField(auto_now_add=True, blank=True)
def __unicode__(self):
return unicode(self.year_id)
class Order(models.Model):
user = models.ForeignKey(User, related_name='who_ordered')
select = models.ManyToManyField(Catalog, related_name='annuals_ordered', blank=True, null=True)
def __unicode__(self):
return unicode(self.user)
Here is the query I've been trying:
Catalog.objects.filter(order__select__annual='2014')
If you need users, you should start with users. Also, you need to filter on a specific field in Annual, ie year_id.
User.objects.filter(order__select__annual__year_id=2014)
If I got your question correctly then, your query is wrong. There is no attribute name order inside your Catalog model, then how can you use it for filtering ? Or I'm missing anything here ?
Directly using the related name references on the related fields, you can get the users by using -
# id is auto generated field or you can pass one annual_product object.
User.objects.filter(who_ordered__select__annual_products__id=1)
# OR
annual = Annual.objects.all()[0]
User.objects.filter(who_ordered__select__annual_products=annual)
The step by step how you can achieve the same :-
# where 1 is the id of an one object in Catalog model.
# You can pass Catalog object also to filter the users
Order.objects.filter(select__id=1)
# Here is the full query
catalog = Catalog.objects.all()[0]
orders = Order.objects.filter(select=catalog)
users = [o.user for o in orders] # This loop isn't necessary.
Now you have all orders specific to one Catalog, from this you can get the user object by using the user attribute in each order.
I'm trying to create a transaction history for each transaction on a Django based marketplace.
I thought the best way of keeping track of this data was to override the save() function and create a Transaction record.
class Transaction(models.Model):
item = models.ManyToManyField(Item, blank=True)
buyer = models.ManyToManyField(User, related_name='buyer')
seller = models.ManyToManyField(User, related_name='seller')
description = models.CharField(max_length=500)
purchase_date = models.DateField(auto_now_add=True)
value = models.DecimalField(max_digits=7, decimal_places=2)
def save(self, *args, **kwargs):
self.buyer.money+=self.value
self.seller.money-=self.value
super(Transaction, self).save(*args, **kwargs)
Am I going about this all wrong? Currenlty I get...
'Transaction' instance needs to have a primary key value before a many-to-many relationship can be used.
You have to save your object before you can go through many-to-many relationships.
Please explain how you can have multiple buyers and sellers on a single transaction. (For the rest of this answer, I'm assuming that there aren't and you meant for these to be ForeignKey fields.)
The related names for buyer and seller are not clear. See below.
I'm not sure what description is for. Is it different from the item list?
item should be called items, since it can be plural, and you might want to create a custom junction table (using the "through" parameter) with a quantity field.
You forgot to save the related objects.
Modified version:
class Transaction(models.Model):
items = models.ManyToManyField(Item, through='TransactionItem', blank=True)
buyer = models.ForeignKey(User, related_name='transactions_as_buyer')
seller = models.ForeignKey(User, related_name='transactions_as_seller')
description = models.CharField(max_length=500)
purchase_date = models.DateField(auto_now_add=True)
value = models.DecimalField(max_digits=7, decimal_places=2)
def save(self, *args, **kwargs):
super(Transaction, self).save(*args, **kwargs)
self.buyer.money += self.value
self.buyer.save()
self.seller.money -= self.value
self.seller.save()
class TransactionItem(models.Model):
transaction = models.ForeignKey(Transaction)
item = models.ForeignKey(Item)
quantity = models.IntegerField()
The buyer and seller fields are many to many fields so self.buyer will never work, I think you were meaning to use ForeignKey instead of ManyToManyField.