How to have continuous ids for multiple models? - python

I wrote my own billing software, but I don't know how to approach this problem.
Right now I have 3 models:
Number
Receipt
MilageReceipt
The point is I need to write two kinds of receipts to my costumers. But for the ministry of finance, they have to have a continuous ID over them. So Number just contains an auto field and Receipt and MilageReceipt just has a Foreign key to that. This way I have an ID over two different models.
But now I want to expand this to also handle multiple companies. So there are two different types of Receipts that need to have a continuous number, but there will be multiple users who all need their own continuous numbers.
I want to have something that results in:
Receipt: company:1, id:1
Receipt: company:2, id:1
Receipt: company:1, id:2
MilageReceipt: company:1, id:3
Receipt: company:2, id:2
MilageReceipt: company:1, id:4
Receipt: company:1, id:5
MilageReceipt: company:2, id:3
I hope it is somewhat clear what I want to achieve. Can you please point me in the direction on how to set up models to get this behavior?
I want to keep the admin as original as possible so I'd like to do this on the model level, not the views - if possible. Right now, for example, I create the Number automatically every time a Receipt or a MilageReceipt is created. So the user doesn't even notice.
Thank you for your help!

For continuous sequences you really can't use database sequences. The easiest solution is to have your own table (Model) to hold the sequences. Here's some code I use to do that:
class Sequence(models.Model):
class Meta:
verbose_name = 'Sequence'
permissions = (('view_sequence', 'Can View Sequence'),)
name = models.CharField(max_length=20)
value = models.IntegerField()
def __str__(self):
return 'Sequence %s=%s' % (self.name, self.value)
def __unicode__(self):
return self.__str__()
#classmethod
def set(cls, name, value=None, increment=0):
with transaction.atomic():
seq = cls.objects.select_for_update().filter(name=name).first()
if not seq:
seq = cls(name=name, value=0)
seq.value = increment + (value if value is not None else seq.value)
seq.save()
return seq.value
#classmethod
def get_next(cls, name):
return cls.set(name, increment=1)
To get the next sequence you would form a key string that names the sequence, eg:
nextnumber = Sequence.get_next('receipt_%d' % company.pk)
Since every company would have a different key, they will be separate sequences.
If you were to use this in let's say your Receipt save method, like so:
class Receipt(models.Model):
def save(self, *args, **kwargs):
if not self.seq_num:
self.seq_num = Sequence.get_next('receipt_%d' % self.company.pk)
super(Receipt, self).save(*args, **kwargs)
company = models.ForeignKey(Company, null=False, Blank=False)
seq_num = models.IntegerField(blank=True, unique=True)
# etc etc
Whenever you save a Receipt, if it doesn't have a seq_num, one will be assigned to it.
There's just one gotcha here, which is that the way the transaction code is structured, there's a possibility of skipping a number if something goes wrong. To avoid that, move the transaction.atomic() block into your receipt save() method.

Related

Peewee - Access an intermediary table easily

Say I have peewee models like so:
class Users(_BaseModel):
id = AutoField(primary_key=True, null=False, unique=True)
first_name = CharField(null=False)
last_name = CharField(null=False)
# Cut short for clarity
class Cohorts(_BaseModel):
id = AutoField(primary_key=True, null=False, unique=True)
name = CharField(null=False, unique=True)
# Cut short for clarity
class CohortsUsers(_BaseModel):
cohort = ForeignKeyField(Cohorts)
user = ForeignKeyField(Users)
is_primary = BooleanField(default=True)
I need to access easily from the user what cohort they are in and for example the cohort's name.
If a user could be in just one cohort, it would be easy but here, having it be many2many complicates things.
Here's what I got so far, which is pretty ugly and inefficient
Users.select(Users, CohortsUsers).join(CohortsUsers).where(Users.id == 1)[0].cohortsusers.cohort.name
Which will do what I require it to but I'd like to find a better way to do it.
Is there a way to have it so I can do Users.get_by_id(1).cohort.name ?
EDIT: I'm thinking about making methods to access them easily on my Users class but I am not really sure it's the best way of doing it nor how to go about it
If it do it like so, it's quite ugly because of the import inside the method to avoid circular imports
#property
def cohort(self):
from dst_datamodel.cohorts import CohortsUsers
return Users.select(Users, CohortsUsers).join(CohortsUsers).where(Users.id == self.id)[0].cohortsusers.cohort
But having this ugly method allows me to do Users.get_by_id(1).cohort easily
This is all covered in the documentation here: http://docs.peewee-orm.com/en/latest/peewee/relationships.html#implementing-many-to-many
You have a many-to-many relationship, where a user can be in zero, one or many cohorts, and a cohort may have zero, one or many users.
If there is some invariant where a user only has one cohort, then just do this:
# Get all cohorts for a given user id and print their name(s).
q = Cohort.select().join(CohortUsers).where(CohortUsers.user == some_user_id)
for cohort in q:
print(cohort.name)
More specific to your example:
#property
def cohort(self):
from dst_datamodel.cohorts import CohortsUsers
cohort = Cohort.select().join(CohortsUsers).where(CohortUsers.user == self.id).get()
return cohort.name

Django - Using signals to make changes to other models

say I have two models like so...
class Product(models.Model):
...
overall_rating = models.IntegerField()
...
class Review(models.Model):
...
product = models.ForeignKey(Product, related_name='review', on_delete=models.CASCADE)
rating = models.IntegerField()
...
I want to use the ratings from all of the child Review objects to build an average overall_rating for the parent Product.
Question: I'm wondering how I may be able to achieve something like this using Django signals?
I am a bit of a newbie to this part of Django; have never really understood the signals part before.
This overall_rating value needs to be stored in the database instead of calculated using a method since I plan on ordering the Product objects based on their overall_rating which is done on a DB level. The method may look something like this if I were to implement it (just for reference):
def overall_rating(self):
review_count=self.review.count()
if review_count >= 1:
ratings=self.review.all().values_list('rating',flat=True)
rating_sum = 0
for i in ratings:
rating_sum += int(i)
return rating_sum / review_count
else:
return 0
Thank you
You want to update your Product after each save of Review. So the best and fastest way would be using post save method. For example, after each saved product you can get all reviews and calculate overall rating and then save it to the Product.
#receiver(post_save, sender=Review, dispatch_uid="update_overall_rating")
def update_rating(sender, instance, **kwargs):
parent = instance.product
all_reviews = Review.objects.filter(product=parent)
parent.overall_rating = get_overall_rating(all_reviews)

Chain lookup through queryset

I have two models: City, and its alias CityAlias. The CityAlias model contains all the names in the City, plus the aliases. What I want is that whenever City is searched by name, the CityAlias model should be queried. This is what I've come up with:
class CityQuerySet(models.QuerySet):
""" If City is searched by name, search it in CityAlias """
def _search_name_in_alias(self, args, kwargs):
for q in args:
if not isinstance(q, models.Q): continue
for i, child in enumerate(q.children):
# q.children is a list of tuples of queries:
# [('name__iexact', 'calcutta'), ('state__icontains', 'bengal')]
if child[0].startswith('name'):
q.children[i] = ('aliases__%s' % child[0], child[1])
for filter_name in kwargs:
if filter_name.startswith('name'):
kwargs['aliases__%s' % filter_name] = kwargs.pop(filter_name)
def _filter_or_exclude(self, negate, *args, **kwargs):
# handles 'get', 'filter' and 'exclude' methods
self._search_name_in_alias(args=args, kwargs=kwargs)
return super(CityQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
class City(models.Model):
name = models.CharField(max_length=255, db_index=True)
state = models.ForeignKey(State, related_name='cities')
objects = CityQuerySet.as_manager()
class CityAlias(models.Model):
name = models.CharField(max_length=255, db_index=True)
city = models.ForeignKey(City, related_name='aliases')
Example: Kolkata will have an entry in City model, and it will have two entries in the CityAlias model: Kolkata and Calcutta. The above QuerySet allows to use lookups on the name field.
So the following two queries will return the same entry:
City.objects.get(name='Kolkata') # <City: Kolkata>
City.objects.get(name__iexact='calcutta') # <City: Kolkata>
So far so good. But the problem arises when City is a ForeignKey in some other model:
class Trip(models.Model):
destination = models.ForeignKey(City)
# some other fields....
Trip.objects.filter(destination__name='Kolkata').count() # some non-zero number
Trip.objects.filter(destination__name='Calcutta').count() # always returns zero
Django internally handles these joins differently, and doesn't call the get_queryset method of City's manager. The alternative is to call the above query as following:
Trip.objects.filter(destination=City.objects.get(name='Calcutta'))
My question is that can I do something, so that however the City model is searched by name, it always searches in the CityAlias table instead?
Or is there another better way to implement the functionality I require?
I think it is better (and more pythonic) to be explicit in what you ask for throughout instead of trying to do magic in the Manager and thus:
City.objects.get(aliases__name__iexact='calcutta') # side note: this can return many (same in original) so you need to catch that
And:
Trip.objects.filter(destination__aliases__name='Calcutta').count()
I was trying to use Custom Lookups but apparently you cannot add a table to the join list. (Well, you could add an extra({"table": ...}) in the model's manager but it's not an elegant solution).
So I'd propose you:
1) Keep always your 'main/preferred' name city also as a CityAlias. So the metadata of the city will be in City... but all the naming information will be in CityAlias. (and maybe change the names)
In this way all look-ups will happen in that table. You could have a boolean to mark which instance is the original/preferred.
class City(models.Model):
state = models.ForeignKey(State, related_name='cities')
[...]
class CityAlias(models.Model):
city = models.ForeignKey(City, related_name='aliases')
name = models.CharField(max_length=255, db_index=True)
2) If you are thinking about translations... Have you thought about django-modeltranslation app?
In this case, it would create a field for each language and it would be always better than having a join.
3) Or, if you are using PostgreSQL, and you are thinking about "different translations for the same city-name" (and I'm thinking with transliterations from Greek or Russian language), maybe you could use PostgreSQL dictionaries, trigrams with similarities, etc. Or even in this case, the 1st approach.
Speaking of keeping it simple. Why not just give the City model a char field 'CityAlias' that contains the string? If I understand your question correctly, this is the most simple solution if you only need one alias per city. It just looks to me as though you are complicating a simple problem.
class City(models.Model):
name = models.CharField(max_length=255, db_index=True)
state = models.ForeignKey(State, related_name='cities')
alias = models.CharField(max_length=255)
c = City.objects.get(alias='Kolkata')
>>>c.name
Calcutta
>>>c.alias
Kolkata

Does a StructuredProperty reference the parent or child?

Does a StructuredProperty reference the parent or child?
class Invoices(ndb.Model): #Child
class Customers(ndb.Model): #Parent
invoice = ndb.StructuredProperty(Invoices)
or...
class Customers(ndb.Model): #Parent
class Invoices(ndb.Model): #Child
customer = ndb.StructuredProperty(Customers)
To answer your question in the context of "what is the better practice for a NoSQL Datastore",
here's what I can offer.
First, you probably want to name your models in the singular, as they should describe a single
Invoice or Customer entity, not several.
Next, using a StructuredProperty implies that you'd like to keep all of this information in a
single entity - this will reduce write/read ops, but can introduce some limitations.
(See the docs -
or this related question)
The most common relationship would be a one(Customer) to many(Invoice) relationship,
which you can structure as below:
class Invoice(ndb.Model): #Child
invoice_id = ndb.StringProperty(required=True) #
class Customer(ndb.Model): #Parent
invoices = ndb.StructuredProperty(Invoices, repeated=True)
def get_invoice_by_id(self, invoice_id):
"""Returns a customer Invoice by invoice_id. Raises KeyError if invoice is not present."""
invoice_matches = [iv for iv in self.invoices if iv.invoice_id == invoice_id]
if not invoice_matches: raise KeyError("Customer has no Invoice with ID %s" % invoice_id)
return invoice_matches[0] # this could be changed to return all matches
Keep in mind the following restrictions of this implementation:
StructuredPropertys can not contain repeated properties inside of themselves.
The complexity for keeping invoice_id globally unique is going to be higher than if Invoice were in its own entity-group. (invoice_key.get() is always better than the query this requires))
You would need an instance method on Customer to find an Invoice by invoice_id.
You would need logic to prevent invoices with the same ID from existing on a single Customer
Here are some of the advantages:
You can query for Customer
Querying an Invoice by invoice_id will return the Customer instance, along with all invoices. (This could be a pro and a con, actually - you need logic to return the invoice from the customer)
Here is a more common solution, but by no means is it necessarily the "right solution."
This solution uses ancestor relationships, that allow you to keep writes to Invoice and
the related Customer atomic - so you could maintain aggregate invoice statistics on the
Customer level. (total_orders, total_gross, etc.)
class Invoice(ndb.Model):
customer = ndb.ComputedProperty(lambda self: self.key.parent(), indexed=False) # when not indexed, this is essentially a #property
class Customer(ndb.Model):
def get_invoice_by_id(self, invoice_id):
"""Returns a customer Invoice by invoice_id. Raises KeyError if invoice is not present."""
invoice_key = ndb.Key(Invoice._get_kind(), invoice_id, parent=self.key)
return invoice_key.get()
def query_invoices(self):
"""Returns ndb.Query for invoices by this Customer."""
return self.query(ancestor=self.key)
invoice = Invoice(parent=customer.key, **invoice_properties)
Good luck with Appengine! Once you get the hang of all of this, it is a truly rewarding platform.
Update:
Here is some additional code for transactionally updating customer aggregate totals as I mentioned above.
def create_invoice(customer_key, gross_amount_paid):
"""Creates an invoice for a given customer.
Args:
customer_key: (ndb.Key) Customer key
gross_amount_paid: (int) Gross amount paid by customer
"""
#ndb.transactional
def _txn():
customer = customer_key.get()
invoice = Invoice(parent=customer.key, gross_amount=gross_amount_paid)
# Keep an atomic, transactional count of customer aggregates
customer.total_gross += gross_amount_paid
customer.total_orders += 1
# batched put for API optimization
ndb.put_multi([customer, invoice])
return invoice
return _txn()
The above code works in a single entity group transaction (e.g. ndb.transactional(xg=False)) because Invoice is a child entity to Customer. If that connection is lost, you would need xg=True. (I'm not sure if it's more expensive, but it is less optimized)

How to make an auto-filled and auto-incrementing field in django admin

[Update: Changed question title to be more specific]
Sorry if I didn't make the question very well, I can't figure how to do this:
class WhatEver():
number = model.IntegerField('Just a Field', default=callablefunction)
...
Where callablefunction does this query:
from myproject.app.models import WhatEver
def callablefunction():
no = WhatEver.objects.count()
return no + 1
I want to automatically write the next number, and I don't know how to do it.
I have errors from callablefunction stating that it cannot import the model, and I think there must be an easier way to do this. There's no need even to use this, but I can't figure how to do it with the pk number.
I've googled about this and the only thing I found was to use the save() method for auto incrementing the number... but I wanted to show it in the <textfield> before saving...
What would you do?
Got it! I hope this will help everyone that has any problems making a auto-filled and auto-incrementing field in django. The solution is:
class Cliente(models.Model):
"""This is the client data model, it holds all client information. This
docstring has to be improved."""
def number():
no = Cliente.objects.count()
if no == None:
return 1
else:
return no + 1
clientcode = models.IntegerField(_('Code'), max_length=6, unique=True, \
default=number)
[... here goes the rest of your model ...]
Take in care:
The number function doesn't take any arguments (not even self)
It's written BEFORE everything in the model
This was tested on django 1.2.1
This function will automatically fill the clientcode field with the next number (i.e. If you have 132 clients, when you add the next one the field will be filled with clientcode number 133)
I know that this is absurd for most of the practical situations, since the PK number is also auto-incrementing, but there's no way to autofill or take a practical use for it inside the django admin.
[update: as I stated in my comment, there's a way to use the primary key for this, but it will not fill the field before saving]
Every Django model already has an auto-generated primary key:
id = models.AutoField(primary_key=True)
It seems you are trying to duplicate an already existing behavior, just use the object primary key.
I, too, came across this problem, my instance of it was customer.number which was relative to the customers Store. I was tempted to use something like:
# Don't do this:
class Customer(models.Model):
# store = ...
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
try:
self.number = self.store.customer_set.count() + 1
else:
self.number = 1
super(Customer, self).save(*args, **kwargs)
The above can cause several problems: Say there were 10 Customers, and I deleted customer number 6. The next customer to be added would be (seemingly) the 10th customer, which would then become a second Customer #10. (This could cause big errors in get() querysets)
What I ended up with was something like:
class Store(models.Model):
customer_number = models.IntegerField(default=1)
class Customer(models.Model):
store = models.ForeignKey(Store)
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
self.number = self.store.customer_number
self.store.number += 1
self.store.save()
super(Customer, self).save(*args, **kwargs)
PS:
You threw out several times that you wanted this field filled in "before". I imagine you wanted it filled in before saving so that you can access it. To that I would say: this method allows you to access store.customer_number to see the next number to come.
You have errors in code, that's why you can't import it:
from django.db import models
class WhatEver(models.Model):
number = models.IntegerField('Just a Field', default=0)
and Yuval A is right about auto-incrementing: you don't even need to declare such a field. Just use the pk or id, they mean the same unless there's a composite pk in the model:
> w = Whatever(number=10)
> w
<Whatever object>
> w.id
None
> w.save()
> w.id
1
[update] Well, I haven't tried a callable as a default. I think if you fix these errors, it must work.

Categories

Resources