How to create the related field? - python

I have a Model you see bellow:
class PriceModel(models.Model):
name = models.CharField(max_length=12)
the PriceModel's id is increased from 1. I want to add other field, which is related to the id. Such as I want it to #PM1, #PM2... means the #PM + id.
How to create the related field?

I want to add other field, which is related to the id. Such as I want it to #PM1, #PM2... means the #PM + id.
First of all, it is not guaranteed that for all database systems, the id is always incremented with one. For example if the database works with transactions, or other mechanisms, it is possible that a transaction is rolled back, and hence the corresponding id is never taken.
If you however want some "field" that always depends on the value of a field (here id), then I think a #property is probably a better idea:
class PriceModel(models.Model):
name = models.CharField(max_length=12)
#property
def other_field(self):
return '#PM{}'.format(self.id)
So now if we have some PriceModel, then some_pricemodel.other_field will return a '#PM123' given (id is 123). The nice thing is that if the id is changed, then the property will change as well, since it is each time calculated based on the fields.
A potential problem is that we can not make queries with this property, since the database does not know such property exists. We can however define an annotation:
from django.db.models import Value
from django.db.models.functions import Concat
PriceModel.objects.annotate(
other_field=Concat(Value('#PM'), 'id')
).filter(
other_field__icontains='pm1'
)

I think you can in your model serializer add a special field, rather than add a field in the Model.
class PriceModelSerializer(ModelSerializer):
....
show_id = serializers.SerializerMethodField(read_only=True)
class Meta:
model = PriceModel
def get_show_id(self):
return '#PM' + self.id

Related

How to write manager class which use filter field as computed field not as a part of model fields?

I have a model Student with manager StudentManager as given below. As property gives the last date by adding college_duration in join_date. But when I execute this property computation is working well, but for StudentManager it gives an error. How to write manager class which on the fly computes some field using model fields and which is used to filter records.
The computed field is not in model fields. still, I want that as filter criteria.
class StudentManager(models.Manager):
def passed_students(self):
return self.filter(college_end_date__lt=timezone.now())
class Student(models.Model):
join_date = models.DateTimeField(auto_now_add=True)
college_duration = models.IntegerField(default=4)
objects = StudentManager()
#property
def college_end_date(self):
last_date = self.join_date + timezone.timedelta(days=self.college_duration)
return last_date
Error Django gives. when I tried to access Student.objects.passed_students()
django.core.exceptions.FieldError: Cannot resolve keyword 'college_end_date' into field. Choices are: join_date, college_duration
Q 1. How alias queries done in Django ORM?
By using the annotate(...)--(Django Doc) or alias(...) (New in Django 3.2) if you're using the value only as a filter.
Q 2. Why property not accessed in Django managers?
Because the model managers (more accurately, the QuerySet s) are wrapping things that are being done in the database. You can call the model managers as a high-level database wrapper too.
But, the property college_end_date is only defined in your model class and the database is not aware of it, and hence the error.
Q 3. How to write manager to filter records based on the field which is not in models, but can be calculated using fields present in the model?
Using annotate(...) method is the proper Django way of doing so. As a side note, a complex property logic may not be re-create with the annotate(...) method.
In your case, I would change college_duration field from IntegerField(...) to DurationField(...)--(Django Doc) since its make more sense (to me)
Later, update your manager and the properties as,
from django.db import models
from django.utils import timezone
class StudentManager(models.Manager):
<b>def passed_students(self):
default_qs = self.get_queryset()
college_end = models.ExpressionWrapper(
models.F('join_date') + models.F('college_duration'),
output_field=models.DateField()
)
return default_qs \
.annotate(college_end=college_end) \
.filter(college_end__lt=timezone.now().date())</b>
class Student(models.Model):
join_date = models.DateTimeField()
college_duration = models.DurationField()
objects = StudentManager()
#property
def college_end_date(self):
# return date by summing the datetime and timedelta objects
return <b>(self.join_date + self.college_duration).date()
Note:
DurationField(...) will work as expected in PostgreSQL and this implementation will work as-is in PSQL. You may have problems if you are using any other databases, if so, you may need to have a "database function" which operates over the datetime and duration datasets corresponding to your specific database.
Personally, I like this solution,
To quote #Willem Van Olsem's comment:
You don't. The database does not know anything about properties, etc. So it can not filter on this. You can make use of .annotate(..) to move the logic to the database side.
You can either do the message he shared, or make that a model field that auto calculates.
class StudentManager(models.Manager):
def passed_students(self):
return self.filter(college_end_date__lt=timezone.now())
class Student(models.Model):
join_date = models.DateTimeField(auto_now_add=True)
college_duration = models.IntegerField(default=4)
college_end_date = models.DateTimeField()
objects = StudentManager()
def save(self, *args, **kwargs):
# Add logic here
if not self.college_end_date:
self.college_end_date = self.join_date + timezone.timedelta(days-self.college_duration)
return super.save(*args, **kwargs)
Now you can search it in the database.
NOTE: This sort of thing is best to do from the start on data you KNOW you're going to want to filter. If you have pre-existing data, you'll need to re-save all existing instances.
Problem
You’re attempting to query on a row that doesn’t exist in the database. Also, Django ORM doesn’t recognize a property as a field to register.
Solution
The direct answer to your question would be to create annotations, which could be subsequently queried off of. However, I would reconsider your table design for Student as it introduces unnecessary complexity and maintenance overhead.
There’s much more framework/db support for start date, end date idiosyncrasy than there is start date, timedelta.
Instead of storing duration, store end_date and calculate duration in a model method. This makes more not only makes more sense as students are generally provided a start date and estimated graduation date rather than duration, but also because it’ll make queries like these much easier.
Example
Querying which students are graduating in 2020.
Students.objects.filter(end_date__year=2020)

How to declare a time-based alphanumerical id field with predefined aplphabetic part (Django model)

I am developing an app in Django.
I wanted to insert in my model an auto-incrementing alphanumerical unique ID field, having, by default, a fixed alphabetical part and an auto-incrementing numerical part. But I also want the availability to change, from admin section, this id to another alphanumerical one, with a different alphanumerical and numerical part.
I tryed to implement this, but it turned out that trying to implement such a field and making it the autofield of the model generates problems in my database.
So I am changing my aim: now I want to implement a time-based alphanumerical unique field with predefined aplphabetic part. Please note: I don't want to overwrite the django default id field, I just want to include in my model a field that gets as default value a unique customized alphanumerical value.
Here is what I did, in my models.py:
def return_timestamped_id():
prefix = "ITCH"
import time
this_time = time.time()
this_time = this_time *10000000
this_time = int(this_time)
timestamp = str(this_time)
default_value = prefix + timestamp
return(default_value)
class object_example(models.Model):
name = models.CharField(max_length=256, blank=True, null=True)
Id_generated = models.CharField(max_length=256, blank=False, null=False, unique=True, default=return_timestamped_id())
The problem is that, as I add objects to this model from the admin section, The
Id_generated is always the same.
I expected that the return_timestamped_id() function was called every time I add a new object. It is clear instead that is called just once and then the same return value is passed to the Id_generated of every new object.
How can I change my code in order to get a different timestamp every time a new object is added?
As you probably saw in the Django docs, you can use either a value or a callable as a default. If you use a callable (e.g. a function) then it will be called each time a default is needed.
The problem: you were passing a value because you were calling your function default=return_timestamped_id(). The function was being called once, when your module (models.py) was imported into the application.
The solution: pass the function itself default=return_timestamped_id
You can see in the django.models.Fields class the relevant code (comments mine):
class Field():
def __init__(self, ..., default=NOT_PROVIDED,...):
...
self.default = default # save the default as a member variable
...
def get_default(self):
"""Return the default value for this field."""
return self._get_default()
#cached_property
def _get_default(self):
if self.has_default():
if callable(self.default): # if it is callable, return it
return self.default
return lambda: self.default # else wrap in a callable

Django: join two table on foreign key to third table?

I have three models
class A(Model):
...
class B(Model):
id = IntegerField()
a = ForeignKey(A)
class C(Model):
id = IntegerField()
a = ForeignKey(A)
I want get the pairs of (B.id, C.id), for which B.a==C.a. How do I make that join using the django orm?
Django allows you to reverse the lookup in much the same way that you can use do a forward lookup using __:
It works backwards, too. To refer to a “reverse” relationship, just use the lowercase name of the model.
This example retrieves all Blog objects which have at least one Entry whose headline contains 'Lennon':
Blog.objects.filter(entry__headline__contains='Lennon')
I think you can do something like this, with #Daniel Roseman's caveat about the type of result set that you will get back.
ids = B.objects.prefetch_related('a', 'a__c').values_list('id', 'a__c__id')
The prefetch related will help with performance in older versions of django if memory serves.

How to use unique_together method in django views

class Model1(models.Model):
username = models.CharField(max_length=100,null=False,blank=False,unique=True)
password = models.CharField(max_length=100,null=False,blank=False)
class Model2(models.Model):
name = models.ForeignKey(Model1, null=True)
unique_str = models.CharField(max_length=50,null=False,blank=False,unique=True)
city = models.CharField(max_length=100,null=False,blank=False)
class Meta:
unique_together = (('name', 'unique_str'),)
I've already filled 3 sample username-password in Model1 through django-admin page
In my views I'm getting this list as
userlist = Model1.objects.all()
#print userlist[0].username, userlist[0].password
for user in userlist:
#here I want to get or create model2 object by uniqueness defined in meta class.
#I mean unique_str can belong to multiple user so I'm making name and str together as a unique key but I dont know how to use it here with get_or_create method.
#right now (without using unique_together) I'm doing this (but I dont know if this by default include unique_together functionality )
a,b = Model2.objects.get_or_create(unique_str='f3h6y67')
a.name = user
a.city = "acity"
a.save()
What I think you're saying is that your logical key is a combination of name and unique_together, and that you what to use that as the basis for calls to get_or_create().
First, understand the unique_together creates a database constraint. There's no way to use it, and Django doesn't do anything special with this information.
Also, at this time Django cannot use composite natural primary keys, so your models by default will have an auto-incrementing integer primary key. But you can still use name and unique_str as a key.
Looking at your code, it seems you want to do this:
a, _ = Model2.objects.get_or_create(unique_str='f3h6y67',
name=user.username)
a.city = 'acity'
a.save()
On Django 1.7 you can use update_or_create():
a, _ = Model2.objects.update_or_create(unique_str='f3h6y67',
name=user.username,
defaults={'city': 'acity'})
In either case, the key point is that the keyword arguments to _or_create are used for looking up the object, and defaults is used to provide additional data in the case of a create or update. See the documentation.
In sum, to "use" the unique_together constraint you simply use the two fields together whenever you want to uniquely specify an instance.

How to check for a specific field change in a model?

I have my models.py :
class Hotel(models.Model):
name = models.CharField(max_length=20)
currency = models.ForeignKey(Currency)
class Currency(models.Mode):
code = models.CharField(max_length=3)
name = models.CharField(max_length=10)
Whenever the currency field in hotel is changing I need to be able to do something. So I have a function like :
#receiver(pre_save,sender=Hotel)
def update_something(sender,**kwargs)
obj = kwargs['instance']
old_object = Hotel.objects.get(pk=obj.pk)
'''
Now I can do anything here comparing the oldo object with the instance
'''
The thing is I don't want to make a query for this, since then the purpose of signals becomes stupid and I become a fool.
So I should be able to do something like :
updated = kwargs['update_fields']
new_currency = updated['currency']
Is their a way I can find out that change for ONLY one particular field say currency ,instead of doing a query like this. I want to get the changes related to the currency foreign key and update things before I save.
Sorry for my bad English and not being able to use too technical terms.
Thanks :)
Rather hacky solution is to save object state on initialization.
from django.forms.models import model_to_dict
class Currency(models.Mode):
code = models.CharField(max_length=3)
name = models.CharField(max_length=10)
def __init__(self):
super(Currency, self).__init__()
self.__state = model_to_dict(self)
def updated(self):
new_state = model_to_dict(self)
return dict(set(self.__state.iteritems()) - set(new_state.iteritems()))
Method updated will return difference between initial and new states.
The point of signals is better decoupling - allow other applications (that you don't necessarily know yet) to transparently hook into your application and/or avoid circular dependencies between application. There's really no point in using a signal here afaik (unless of course the signal handler is in another app and you don't want the app with the Hotel and Currency models to depend on this other app).
To answer your question: doing a query to retrieve the original state is the only way you can compare stored value and current value. Now if you're only interested in one specific field, you of course don't have to retrieve the whole model instance - just use queryset.values_list:
old_currency = Hotel.objects.filter(pk=obj.pk).values_list("currency", flat=True)[0]

Categories