I am trying to get a field value automatically pulled from different table. Below is my case: I have two tables: sales and returns. While entering a new return, when a "sales_id" is selected, want to show "sales_quantity" automatically populated, non-editable (and also if possible, want to constraint the "return_quantity" <= "sales_quantity").
class T_Sales(models.Model):
product = models.ForeignKey(P_Product)
sales_quantity = models.IntegerField()
def __unicode__(self):
return str(self.id)
class T_Return(models.Model):
sales_id = models.ForeignKey(T_Sales)
#sales_quantity = models.IntegerField(default=T_Sales.sales_quantity)
return_quantity = models.IntegerField()
def __unicode__(self):
return self.description
Creating property in your model will do the job. sales_id won't be null, so there will never be an issue when trying to get that value.
class T_Sales(models.Model):
product = models.ForeignKey(P_Product)
sales_quantity = models.IntegerField()
def __unicode__(self):
return str(self.id)
class T_Return(models.Model):
sales_id = models.ForeignKey(T_Sales)
return_quantity = models.IntegerField()
def __unicode__(self):
return self.description
#property
def sales_quantity(self):
return self.sales_id.sales_quantity
Pros of that method:
value is not stored in your database twice
value will be automatically updated if there is change on T_Sales object.
Cons of that method:
It will trigger separate query when fetching that field, unless use select_related in your queryset.
it is hard (but possible) to print that field in detail view inside django admin (you can always show it only on list).
If you wan't to create some validation, you can override model's clean method and do your comparsion here, if there is something wrong, you should throw ValidationError. Example:
from django.core.exceptions import ValidationError
class T_Return(models.Model):
# ......
def clean(self):
if self.return_quantity > self.sales_quantity:
raise ValidationError("You can't return more than it was bought")
return super(T_Return, self).clean()
You can also assign error to return_quantity field, just pass dict in form 'field_name': error into validation error:
raiseValidationError({'return_quantity': _('You can't return more than it was bought.')}
You can't assign that error to sales_quantity.
You can create separate field in T_Return model and copy value from T_Sales on save:
class T_Sales(models.Model):
product = models.ForeignKey(P_Product)
sales_quantity = models.IntegerField()
def __unicode__(self):
return str(self.id)
class T_Return(models.Model):
sales_id = models.ForeignKey(T_Sales)
sales_quantity = models.IntegerField(editable=False)
return_quantity = models.IntegerField()
def __unicode__(self):
return self.description
def save(self, *args, **kwargs):
if not self.sales_quantity:
self.sales_quantity = self.sales_id.sales_quantity
supr(T_Return, self).save(*args, **kwargs)
Pros of that method:
It will trigger additional query to T_Sales only on (first) save.
It is easy to display value in admin's detail view
Cons of that method:
- You're storing value in database twice
- If value in T_Sales object will change, value in T_Return won't be changed automatically (that can be fixed by simple trigger on save of T_Sales, but only inside django ORM)
If you wan't to create some validation, you can override model's clean method and do your comparsion here, if there is something wrong, you should throw ValidationError. Example:
from django.core.exceptions import ValidationError
class T_Return(models.Model):
# ......
def clean(self):
if self.return_quantity > self.sales_quantity:
raise ValidationError("You can't return more than it was bought")
return super(T_Return, self).clean()
You can also assign error to return_quantity or to sales_quantity field, just pass dict in form 'field_name': error into validation error:
raiseValidationError({'return_quantity': _('You can't return more than it was bought.')}
Assigning that errot both to return_quantity and sales_quantity will show that error twice.
Related
I have a model which looks like this.
import uuid
from django.db import models
class TemplateId(models.Model):
id = models.SmallAutoField(primary_key=True, serialize=False, verbose_name='ID')
template_name = models.CharField(max_length=255, default="")
template_id = models.UUIDField(max_length=255, default=uuid.UUID, unique=True)
def __str__(self):
return str(self.template_name)
class Meta:
ordering = ('-id',)
I have another function/code where I'm calling an API to fetch the template_name and template_id and store it in dB. But every time when I get a response from the API, I want to override the the whole table (everytime deleting old record and then adding the new records)
currently I'm doing this:
def fetch_template_id():
api_response = # calling the API
for template_id in api_response:
obj = TemplateId(template_id=template_id["id"], template_name=template_id["name"])
obj.save()
In order to override, I tried overriding the save method in my TemplateId model as below but it didn't work
def save(self, *args, **kwargs):
super(TemplateId, self).save(*args, **kwargs)
Since the data gets saved in the model fields by getting the API response, next time when same data is received from the API response, it throws duplicate data error in the dB.
How do I override all the dB fields with each API call?
If the objects exists, update it. If it doesn't, create it.
def fetch_template_id():
api_response = # calling the API
for template_id in api_response:
try:
obj = TemplateId.objects.get(template_id=template_id["id"])
obj.template_name=template_id["name"]
except:
obj = TemplateId(template_id=template_id["id"], template_name=template_id["name"])
obj.save()
If by override you mean update the template_name of a particular template id, you need to change:
obj = TemplateId(template_id=template_id["id"], template_name=template_id["name"])
obj.save()
to
obj = TemplateId.objects.get(id=template_id["id"])
obj.template_name = template_id["name"]
obj.save()
Because in your case you are searching for a particular entry with the 2 conditions, but do not change anything when saving it. save method can stay as is, not need to override it
I create a custom MultilingualCharField and I want order the instances by it, in the right language. I prefer to do so in the model (tell me if this wasn't a good idea), is it possible?
class Myclass(models.Model):
name = MultilingualCharField(max_length=32, unique=True)
...
def __str__(self):
name_traslated={'name_it': self.name_it, 'name_en': self.name_en}
name_verbose=_('name_it')
return name_traslated[name_verbose]
class Meta:
#name_traslated={'name_it': self.name_it, 'name_en': self.name_en}
name_verbose=_('name_it')
ordering = [name_verbose]
#ordering = [name_traslated[name_verbose]]
__str__ is working but ordering is not: it gives TypeError: 'class Meta' got invalid attribute(s): name_verbose
My MultilingualCharField create two columns: name_it and name_en and I want to order the istances on one of these. If you need it here's the code (from Web Development with Django Cookbook):
class MultilingualCharField(models.CharField):
def __init__(self, verbose_name=None, **kwargs):
self._blank = kwargs.get("blank", False)
self._editable = kwargs.get("editable", True)
#super(MultilingualCharField, self).__init__(verbose_name, **kwargs)
super().__init__(verbose_name, **kwargs)
def contribute_to_class(self, cls, name, virtual_only=False):
# generate language specific fields dynamically
if not cls._meta.abstract:
for lang_code, lang_name in settings.LANGUAGES:
if lang_code == settings.LANGUAGE_CODE:
_blank = self._blank
else:
_blank = True
localized_field = models.CharField(string_concat(
self.verbose_name, " (%s)" % lang_code),
name=self.name,
primary_key=self.primary_key,
max_length=self.max_length,
unique=self.unique,
blank=_blank,
null=False,
# we ignore the null argument!
db_index=self.db_index,
rel=self.rel,
default=self.default or "",
editable=self._editable,
serialize=self.serialize,
choices=self.choices,
help_text=self.help_text,
db_column=None,
db_tablespace=self.db_tablespace
)
localized_field.contribute_to_class(cls,
"%s_%s" % (name, lang_code),)
def translated_value(self):
language = get_language()
val = self.__dict__["%s_%s" % (name, language)]
if not val:
val = self.__dict__["%s_%s" % (name, settings.LANGUAGE_CODE)]
return val
setattr(cls, name, property(translated_value))
Thank you
I have a model with a field name. My custom MultilingualCharField create in the database a field for each language (name_en, name_it etc). I don't have a field name in the database, but only in the model.
So, here what I did (I think can be useful even if you don't use a custom field but a field for each language in the model):
In mymodel.py:
class MyClass(models.Model):
...
class Meta:
ordering = [_('name_it')]
This works in form etc but gives an error in admin (TypeError: expected string or bytes-like object), so here's my Admin.py:
class MyClassAdmin(admin.ModelAdmin):
def get_ordering(self, request):
if get_language_from_request(request)=='it':
return ['name_it']
else:
return ['name_en']
If you find an error or a better way please tell me
Model:
ATTN_TYPE_CHOICES = (
('N', 'Entry'),
('X', 'Exit'),
('L', 'Leave'),
)
class Attn(Timestamp):
emp_id = models.CharField(
max_length=10
)
date = models.DateField()
time = models.TimeField(
default=time(00, 00)
)
type = models.CharField(
max_length=1,
choices=ATTN_TYPE_CHOICES,
default='N'
)
#property
def late(self):
return type == 'N' and self.time > LATE_LIMIT
def save(self, *args, **kwargs):
try:
Attn.objects.get(emp_id=self.emp_id, date=self.date, type='N')
except Attn.DoesNotExist:
pass
else:
try:
exit = Attn.objects.get(emp_id=self.emp_id, date=self.date, type='X')
except Attn.DoesNotExist:
self.type = 'X'
else:
exit.delete()
super(Attn, self).save(*args, **kwargs)
class Meta:
unique_together = ('emp_id', 'date', 'type')
I will create objects thrice. The first time is simple. The type will be N. The second time I want the save method to check if type N already exists, if it does, then change the type to 'X' and save second object. Third time, I want it to check for N and then for X. But this time it will find X and will delete the existing entry for X before saving the new entry with type X.
For some reason, the code seems to get stuck at the unique_together and doesn't let me save data from the admin panel. Should I try and catch the Integrityerror for this problem?
Try editing the save method like this,
def save(self, *args, **kwargs):
try:
Attn.objects.get(emp_id=self.emp_id, date=self.date, type='N')
try:
exit = Attn.objects.get(emp_id=self.emp_id, date=self.date, type=='X')
exit.delete()
except Attn.DoesNotExist:
self.type = 'X'
else:
self.type = 'X'
except Attn.DoesNotExist:
self.type = 'N'
return super(Attn, self).save(*args, **kwargs)
Remove the unique_together constraint, its not needed now, you are explicitly overriding the save method and restricting the app to save objects with the conditions above..
EDIT
From the docs,
The ValidationError raised during model validation when the constraint is violated has the unique_together error code.
That means, if the unique_together constraint is violated then, the ValidationError is raised in the model validation itself. Django never even try to reach near the save method, if the constraint is failed. Thus, django-admin raises error before committing the object to the database.
If I have:
class Info(Model):
...
class Ad(Model):
listed_date = DatetimeField()
info = ForeignKey('Info', related_name='ads', null=True)
....
I want to query Info based on fields within Ad, but only the latest ad. I know I can do:
Ad.objects.latest('listed_date')
But since I will be building up the query by chaining several filter/excludes together, I want something like:
query = query.filter(
Q(**{
'ads__latest__'+attr_name: query_value
})
)
Or perhaps even have a field 'latest_ad' which always points to the most recent based on a certain field. The goal is to be able to query just the latest in the related field in a built up filter/exclude method.
How can I do this?
EDIT:
A little background...
I have 2 models (LegalAd, TrusteeInfo) that store scraped data about the same auction item, some of the field need a fair deal of processing to extract the necessary values (hence my decision to store the information in separate models) store the data at different stages of processing. I then attempt to combine both models into one (AuctionItem), and use properties extensively to prioritze data from TrusteeInfo over LegalAd for the similar fields they share. The problem is that I would like to query those fields, which the use of properties prohibits. So I created a manager and overrode the filter and exclude methods to hold the prioritization logic. Below is the code:
class LegalAd(models.Model):
listed_date = models.DateField(null=True) # field I would like to use for latest query
auction = models.ForeignKey('auction_table.Auction', related_name='legal_ads', null=True)
...
class TrusteeInfo(models.Model):
auction = models.OneToOneField('auction_table.Auction', null=True)
...
class AuctionManager(models.Manager):
def do_query_action(self, action, kwargs):
trusteeinfo = apps.get_model('scrapers', 'TrusteeInfo')
trustee_fields = [field.name for field in trusteeinfo._meta.get_fields()]
legalad = apps.get_model('scrapers', 'LegalAd')
legalad_fields = [field.name for field in legalad._meta.get_fields()]
related_fields = trustee_fields + legalad_fields
auction_native_fields = [
'legal_ads',
'trusteeinfo',
'properties',
'id',
'pk',
'created_date',
'updated_date'
]
query = super(AuctionManager, self)
for attr, value in kwargs.items():
attr_base = attr.split('__')[0] # get the base attr name
if attr_base in auction_native_fields:
query = getattr(query, action)(**{attr: value})
elif attr_base in related_fields:
qs = []
if attr_base in trustee_fields:
trustee_attr_name = 'trusteeinfo__' + attr
qs.append(Q(**{trustee_attr_name: value}))
if attr_base in legalad_fields:
legalad_attr_name = 'legalads__' + attr
qs.append(Q(**{legalad_attr_name: value}))
query = getattr(query, action)(reduce(or_, qs))
else:
raise AttributeError("type object `Auction` has no attribute '{attr}'".format(attr=attr))
return query.distinct()
def filter(self, **kwargs):
return self.do_query_action('filter', kwargs)
def exclude(self, **kwargs):
return self.do_query_action('exclude', kwargs)
class Auction(models.Model):
objects = AuctionManager()
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
#property
def latest_ad(self):
return self.legal_ads.exists() and self.legal_ads.latest('listed_date')
#property
def sale_datetime(self):
if self.trusteeinfo and self.trusteeinfo.sale_datetime:
return self.trusteeinfo.sale_datetime
else:
return self.latest_ad and self.latest_ad.sale_datetime
#property
def county(self):
if self.trusteeinfo and self.trusteeinfo.county:
return self.trusteeinfo.county
else:
return self.latest_ad and self.latest_ad.county
#property
def sale_location(self):
return self.latest_ad and self.latest_ad.sale_address
#property
def property_addresses(self):
if self.trusteeinfo and self.trusteeinfo.parsed_addresses.exists():
return self.trusteeinfo.parsed_addresses
else:
return self.latest_ad and self.latest_ad.parsed_addresses
#property
def raw_addresses(self):
if self.trusteeinfo and self.trusteeinfo.addresses:
return self.trusteeinfo.addresses
else:
return self.latest_ad and self.latest_ad.addresses.get('addresses', None)
#property
def parcel_numbers(self):
return self.latest_ad and self.latest_ad.parcel_numbers
#property
def trustee(self):
if self.trusteeinfo:
return self.trusteeinfo.trustee
else:
return self.latest_ad and self.latest_ad.trustee.get('trustee', None)
#property
def opening_bid(self):
if self.trusteeinfo and self.trusteeinfo.opening_bid:
return self.trusteeinfo.opening_bid
else:
return self.latest_ad and self.latest_ad.dollar_amounts.get('bid_owed', [[None]])[0][0]
#property
def deposit_amount(self):
if self.trusteeinfo and self.trusteeinfo.deposit_amount:
return self.trusteeinfo.deposit_amount
else:
return self.latest_ad and self.latest_ad.dollar_amounts.get('deposit', [[None]])[0][0]
#property
def sale_status(self):
return self.trusteeinfo and self.trusteeinfo.sale_status
#property
def trustors(self):
if self.trusteeinfo and self.trusteeinfo.parsed_names.exists():
return self.trusteeinfo.parsed_names
else:
return self.latest_ad and self.latest_ad.parsed_names
It gets a bit more complicated with the fact that the ads are usually listed 2 at a time so there is a good chance of 2 ads showing up for the latest date, meaning I would have to run something like a first() method on it too. I could look out for certain kwargs and run a special query for that but how would I incorporate that into the the rest of the kwargs in the chained query? Ideally, if I could keep the one to many legal_ads, but also be able to do something like:
query.filter(latest_ad__<queryfield>=value)
or:
query.filter(legal_ads__latest__<queryfield>=value)
That would be great.
What you have is the so called greatest-n-per-group problem, its hard to deal with or even impossible with the ORM.
One way to approach the problem can be found here.
In your case it could be something like this:
Info.objects.filter(
ad__listed_date__in=Info.objects.annotate(
last_date=Max('ad__listed_date')
).values_list('last_date', flat=True)
#now you can add more
#ad__<somefiled> statements
#but you need to make it in a single `.filter` call
#otherwise the ORM will do separate joins per `.filter` call
)
I personally don't like this. It's looks like a hack to me, its not very efficient and it can very easy return bad results if a penultimate ad in some group have an equal listed_date to a last ad in another group.
Workarounds
If you give us some more background about why do you need to filter on the latest_ad per info, maybe we could find another way to get the same/similar results.
However, one workaround which I prefer, is to filter on some date_range. For example, don't search for the latest_ad, but .filter on the latest_ads in last_day or two or a week, depending on your needs. Its pretty easy and efficient (easy to optimize) query.
Info.objects.filter(
ad__listed_date__gte=(today-timedelta(days=1))
#now again you can keep adding more `ad__<somefiled>` statements
#but make sure to enclose them in a single `.filter` call.
)
You also mention a good workaround, if you can easy keep up-to-date Info.latest_ad field, then I guess you will be good to go.
If you go for that approach make sure to set on_delete=models.SET_NULL because the default behavior (cascade delete) can bring you problems.
class Info(Model):
#...
#related_name='+' prevents creating a reverse relation
#on_delete=models.SET_NULL prevents deleting the Info object when its latest ad got deleted.
latest_ad = models.ForeignKey('Ad',
related_name='+',
blank=True,
null=True,
on_delete=models.SET_NULL
)
You can use .latest() along .filter()
Ad.objects.filter(your_filter=your_value).latest('listed_date')
or using oder_by
Ad.objects.filter(your_filter=your_value).order_by('-listed_date')[0]
I want to be able to return different values from a Queryset (not changing the database) based on a parameter. I'd like to be able to write it into the model so that it is enforced everywhere.
If the user is a certain type of user, I would want a QuerySet field to be blank (or "hidden" or something similar).
Here's a simplified Model:
class SomeDetails(models.Model):
size = models.FloatField()
this_is_okay_to_show = models.TextField()
not_always_ok = models.TextField()
Simplified Queryset:
qsSomeDetails = SomeDetails.objects.all()
I want not_always_ok to either return the text value stored in the database or return an empty string (or 'hidden' or similar).
Template Filter would work, but it really needs to be in the model.
I'm not sure how to pass a parameter through to make it work.
I feel like the answer is right in front of me, but I'm just not seeing it.
define a Manager class for SomeDetails model:
class SomeDetailsManager(models.Manager):
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
return getattr(self.get_query_set(), attr, *args)
def get_query_set(self):
return self.model.QuerySet(self.model)
And change your SomeDetails like this:
from django.db.models.query import QuerySet
class SomeDetails(models.Model):
size = models.FloatField()
this_is_okay_to_show = models.TextField()
not_always_ok = models.TextField()
objects = SomeDetailsManager()
class QuerySet(QuerySet):
def get_user(self, user, *args, **kwargs):
_objects = self.filter(*args, **kwargs)
for obj in _objects:
if user.username = 'foo': #change with your condition
obj.not_always_ok = ''
return _objects
Now you can use
qsSomeDetails = SomeDetails.objects.filter(pk=1).get_new_objects(request.user)
note:
first done all your filter and use get_new_objects(request.user) as last part.