In my model I need an ID field that is different from the default ID given by Django. I need my IDs in the following format: [year][ascending number]
Example: 2021001,2021002,2021003
The IDs shall not be editable but model entries shall take the year for the ID from a DateTimeField in the model. I am unsure if I use a normal Django ID and also create some kind of additional ID for the model or if I replace the normal Django ID with a Custom ID.
This problem is pretty similar to one I had solved for a previous project of mine. What I had done for this was to simply use the default id for the primary key, while using some extra fields to make the composite identifier needed.
To ensure the uniqueness and the restarting of the count I had made a model which would (only by the logic, no actual constraints) only have one row in it. Whenever a new instance of the model which needs this identifier would be created this row would be updated in a transaction and it's stored value would be used.
The implementation of it is as follows:
from django.db import models, transaction
import datetime
class TokenCounter(models.Model):
counter = models.IntegerField(default=0)
last_update = models.DateField(auto_now=True)
#classmethod
def get_new_token(cls):
with transaction.atomic():
token_counter = cls.objects.select_for_update().first()
if token_counter is None:
token_counter = cls.objects.create()
if token_counter.last_update.year != datetime.date.today().year:
token_counter.counter = 0
token_counter.counter += 1
token_counter.save()
return_value = token_counter.counter
return return_value
def save(self, *args, **kwargs):
if self.pk:
self.__class__.objects.exclude(pk=self.pk).delete()
super().save(*args, **kwargs)
Next suppose you need to use this in some other model:
class YourModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
yearly_token = models.IntegerField(default=TokenCounter.get_new_token)
#property
def token_number(self):
return '{}{}'.format(self.created_at.year, str(self.yearly_token).zfill(4))
#classmethod
def get_from_token(cls, token):
year = int(token[:4])
yearly_token = int(token[4:])
try:
obj = cls.objects.get(created_at__year=year, yearly_token=yearly_token)
except cls.DoesNotExist:
obj = None
return obj
Note: This might not be very refined as the code was written when I was very inexperienced, and there may be many areas where it can be refined. For example you can add a unique_for_year in the yearly_token field so:
yearly_token = models.IntegerField(default=TokenCounter.get_new_token, unique_for_year='created_at')
Related
I am try to refer 'spot_price' of model 'Spot' in model 'Basis' in django model layer, How can I manage this?
I have designed view.py to automaticaly render the templates. so I am not able to modifty any view.py to choose data like 'models.B.objects.get().field'.
and more, str is set to indicate the date, so, in the django backstage admin, the 'spot' field display would be 'date' formate, how would be change to 'spot_price'?
model Spot
class Spot(models.Model):
date = models.DateField(primary_key=True)
spot_price = models.FloatField(blank=True, null=True)
def __str__(self):
return str(self.date) if self.date else ''
need to refer the model Spot'spot_price by date, cause date is unique but spot_price is not
class Basis(models.Model):
date = models.DateField(primary_key=True)
major_future_contract_close_price = models.FloatField(blank=True)
spot = models.OneToOneField(Spot, on_delete=models.CASCADE)
basis = models.FloatField(default=calculate_basis)
def __str__(self):
return str(self.date) if self.date else ''
def calculate_basis(self):
return abs(self.major_future_contract_close_price -
self.spot.spot_price)
I expect the Basis.query.data would to like 'date: 2019-04-25, major_future_contract_close_price: 100.0, spot: 96.5, basis: 3.5'
You can't use class method as default, because it requires self, which is not existing when you are still creating the object.
If you need to have it stored in field (database), override default save() method or use signals to modify the basis field once your object is created. Also note that you have to recalculate basis every time close_price or spot_price changes, as the value is just written in database.
Probably better solution would be to use #property so for anyone that will be using you model it will look like a field, but it will dynamically calculate value based on current data.
For example, I'm overriding save() to calculate the basis field. I set it as editable=False, which means it won't appear in forms by default (and you will not be able to see it in admin!). You can safely remove that part if you want to just look at the values.
Additionally I add basis_as_property property.
class Basis(models.Model):
date = models.DateField(primary_key=True)
major_future_contract_close_price = models.FloatField(blank=True)
spot = models.OneToOneField(Spot, on_delete=models.CASCADE)
basis = models.FloatField(editable=False, blank=True)
#property
def basis_as_property(self):
return '%s' % (self.calculate_basis())
def __str__(self):
return str(self.date) if self.date else ''
def save(self, *args, **kwargs):
if not self.basis:
self.basis = self.calculate_basis()
super(Basis, self).save(*args, **kwargs)
def calculate_basis(self):
return abs(self.major_future_contract_close_price - self.spot.spot_price)
As for Spot str repr, I don't think it's possible to change it based on where it is referenced. If you want to use spot_price, you can just use: return str(self.spot_price) if self.spot_price else str(self.date)
Yeah I know, it's not possible. Or maybe I didn't see.
But, I'm gonna explain why I need this. Let's do some dummy classes:
class A(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
lvl_struct = GenericForeignKey('content_type', 'object_id')
Let's say A can be attached to a struct (logical struct, like department in jobs). An A instance can be attached to only one struct, but there're 4 disctinct type of struct, and that's where I discovered generic foreign key (instead of a polymorphism on A).
But now, my problem is, in my form, I want to attach the actual struct when I create a A instance :
class CreateAForm(forms.ModelForm)
lvl_struct = forms.ModelChoiceField(
queryset=None, #here is my problem
required=True
)
So here I would like to have a unique select with all possibilities (all instances of first struct type, all instances of second struct type and so on).
So is there any way to do this, or will I have to do like four select with some js to check at least one and only one select has a value?
Or of course a third solution which I didn't see. Tell me.
Thank you in advance for your time.
For this task I am using ChoiceField instead of ModelChoiceField. Convert all the querysets of your objects into combined list inside of form init, then create list with display names and assign it to choices like:
class MyForm(forms.ModelForm):
content_object = forms.ChoiceField(label=_('Object'), widget=forms.Select(required=True)
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
# combine object_type and object_id into a single 'generic_obj' field
# getall the objects that we want the user to be able to choose from
available_objects = list(ModelOne.objects.filter(...))
available_objects += list(ModelTwo.objects.filter(...))
available_objects += list(ModelThree.objects.filter(...))
# now create our list of choices for the <select> field
object_choices = []
for obj in available_objects:
type_id = ContentType.objects.get_for_model(obj.__class__).id
obj_id = obj.id
form_value = "type:%s-id:%s" % (type_id, obj_id) # e.g."type:12-id:3"
display_text = str(obj)
object_choices.append([form_value, display_text])
self.fields['content_object'].choices = object_choices
When you save the form you have to decode the string to get chosen content_type and object_id. After that you can assign them back to instance. You can do it with:
from django.contrib.contenttypes.models import ContentType
def save(self, *args, **kwargs):
# get object_type and object_id values from combined generic_obj field
object_string = self.cleaned_data['content_object']
matches = re.match("type:(\d+)-id:(\d+)", object_string).groups()
object_type_id = matches[0] # get 45 from "type:45-id:38"
object_id = matches[1] # get 38 from "type:45-id:38"
object_type = ContentType.objects.get(id=object_type_id)
self.instance.object_id = object_id
self.instance.content_type = object_type
super(MyForm, self).save(*args, **kwargs)
I have a conceptual problem I dont know how to handle. I want to give a new id (starting at 0) to a subset of a table based on a unique_together constaint
To conceptualize my problem, lets take a Books and Chapters model
class Books(models.Model):
name = models.CharField(max_length=255, db_index=True)
class Chapters(modes.Model):
book = models.ForeignKey(Books)
chapter_id = models.PositiveIntegerField(db_index=True)
def Meta(self):
unique_together = (("book", "chapter_id"),)
In resume, I want to give to every chapters of a book a chapter_id from 0 to n
Is there a way to do that in django? Actually, I was doing something like below to increment the chapter_id in the save method of the Chapters model. The problem is sometimes chapters are saved simultaneously and the same chapter_id is given to many chapters and the unique_together constraint prevent it to be saved.
def cal_chapter_id(book):
chapters = Feature.objects.filter(book=book).aggregate(Max('chapter_id'))
if chapters['chapter_id__max'] is not None:
return chapters['chapter_id__max'] + 1
else:
return 0
def save(self, *args, **kwargs):
if self.chapter_id is None:
self.chapter_id = cal_chapter_id(self.book)
super(Chapters, self).save(*args, **kwargs)
How to move stored procedure to django model class and use them in filter/exclude?
As said here What is the best way to access stored procedures in Django's ORM it should be possible.
In another word, how can i accomplish something like this:
class Project(models.Model):
name = models.CharField()
def is_finished(self):
count = self.task_set.all().count()
count2 = self.task_set.filter(finished=True).count()
if count == count2:
return True
else:
return False
class Task(models.Model):
name = models.CharField()
finished = models.BooleanField()
project = models.ForeignKey(Project)
#somewhere else in the code...
finished_projects = Project.objects.filter(is_finished=True)
Not sure why you are referring to stored procedures in this context.
But if i understand your example correct, your problem is that you can filter only by modelfields that have a corresponding field in a database table.
And therefore you can't use django's orm to filter by methods and properties.
But you can achieve what you want using a list comprehension:
finished_projects = [p for p in Project.objects.all() if p.is_finished()]
One solution is denormalization:
class Project(models.Model):
name = models.CharField()
is_finished = models.BooleanField()
def _is_finished(self):
return self.task_set.exclude(finished=True).exists()
def update_finished(self):
self.is_finished = self._is_finished()
self.save()
class Task(models.Model):
name = models.CharField()
finished = models.BooleanField()
project = models.ForeignKey(Project)
def save(*args, **kwargs):
res = super(Task, self).save(*args, **kwargs)
self.project.update_finished()
return res
#somewhere else in the code...
finished_projects = Project.objects.filter(is_finished=True)
It is nice if you have much more reads than writes because reads will be very fast (faster than e.g. using stored procedures). But you should take care of consistency yourselves.
Django's aggregates or 'raw' support can be often used to implement stored procedure logic.
In Django (1.0.2), I have 2 models: Lesson and StatLesson.
class Lesson(models.Model):
contents = models.TextField()
def get_visits(self):
return self.statlesson_set.all().count()
class StatLesson(models.Model):
lesson = models.ForeignKey(Lesson)
datetime = models.DateTimeField(default=datetime.datetime.now())
Each StatLesson registers 1 visit of a certain Lesson. I can use lesson.get_visits() to get the number of visits for that lesson.
How do I get a queryset of lessons, that's sorted by the number of visits? I'm looking for something like this: Lesson.objects.all().order_by('statlesson__count') (but this obviously doesn't work)
Django 1.1 will have aggregate support.
On Django 1.0.x you can count automatically with an extra field:
class Lesson(models.Model):
contents = models.TextField()
visit_count = models.IntegerField(default=0)
class StatLesson(models.Model):
lesson = models.ForeignKey(Lesson)
datetime = models.DateTimeField(default=datetime.datetime.now())
def save(self, *args, **kwargs):
if self.pk is None:
self.lesson.visit_count += 1
self.lesson.save()
super(StatLesson, self).save(*args, **kwargs)
Then you can query like this:
Lesson.objects.all().order_by('visit_count')
You'll need to do some native SQL stuff using extra.
e.g. (very roughly)
Lesson.objects.extra(select={'visit_count': "SELECT COUNT(*) FROM statlesson WHERE statlesson.lesson_id=lesson.id"}).order_by('visit_count')
You'll need to make sure that SELECT COUNT(*) FROM statlesson WHERE statlesson.lesson_id=lesson.id has the write db table names for your project (as statlesson and lesson won't be right).