I use a snippet in http://www.djangosnippets.org/snippets/1034/ for my Model inheritance. It works fine at the first. However, after I delete some elements in database, the code works wrong.
As I debug, I found that the problem is reside in the method: as_leaf_class.
In the following code:
if (model == Meal):
return self
return model.objects.get(id=self.id)
the last line will raise exception when the element is deleted.
Anyone could give a solution for this?
Model inheritance with content type and inheritance-aware manager
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.db.models.query import QuerySet
class SubclassingQuerySet(QuerySet):
def __getitem__(self, k):
result = super(SubclassingQuerySet, self).__getitem__(k)
if isinstance(result, models.Model) :
return result.as_leaf_class()
else :
return result
def __iter__(self):
for item in super(SubclassingQuerySet, self).__iter__():
yield item.as_leaf_class()
class MealManager(models.Manager):
def get_query_set(self):
return SubclassingQuerySet(self.model)
class Meal (models.Model) :
name = models.TextField(max_length=100)
content_type = models.ForeignKey(ContentType,editable=False,null=True)
objects = MealManager()
def save(self, *args, **kwargs):
if(not self.content_type):
self.content_type = ContentType.objects.get_for_model(self.__class__)
super(Meal, self).save(*args, **kwargs)
def as_leaf_class(self):
content_type = self.content_type
model = content_type.model_class()
if (model == Meal):
return self
return model.objects.get(id=self.id)
class Salad (Meal) :
too_leafy = models.BooleanField(default=False)
objects = MealManager()
I don't know if that snippet is still relevant now that you can use abstract base classes.
This lets you declare a model that is not a db table but that other models can inherit from.
First answer: Why are you trying to call as_leaf_class on a deleted object? If it hurts when you do that, don't do it.
The second answer is that you could wrap the failing line with try...except Meal.DoesNotExist, and return None or self or something.
Related
I'm trying to "inject" some raw sql into my DRF nested Serializer:
# SERIALIZERS
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = '__all__'
class DriverSerializer(serializers.ModelSerializer):
car = CarSerializer() # <--- here I don't want to get the Car object but rather inject a raw sql.
class Meta:
model = Driver
fields = '__all__'
The SQL injection is needed to request for a specific version of the data since I'm using MariaDB versioning tables but this is not relevant. How do I override the method that gets the object from CarSerializer? Thank you.
This is untested but I think you want to override the __init__ in DriverSerializer and then load the result of your raw SQL via data, something like this:
class DriverSerializer(serializers.ModelSerializer):
[...]
def __init__(self, *args, **kwargs):
super(DriverSerializer, self).__init__(*args, **kwargs)
name_map = {'column_1': 'obj_attr_1', 'column_2': 'obj_attr_1', 'pk': 'id'}
raw = Car.objects.raw('SELECT ... FROM ...', translations=name_map)
data = {k: getattr(raw[0], k) for k in name_map.keys()}
self.car = CarSerializer(data=data)
You could define method under your model to get related Car
class Car(models.Model):
def current_car(self):
return Car.objects.raw('SELECT ... FROM ...')[0]
Then in serializer you could reuse following method
class DriverSerializer(serializers.ModelSerializer):
car = CarSerializer(source="current_car")
class Meta:
model = Driver
fields = (...)
Thank you everyone for your answers, I managed to make it work although my solution is not as clean as the one suggested from #yvesonline and #iklinak:
I first checked the official DRF documentation on overriding serializers: https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior
In particular I was interested in the overriding of the method: .to_representation(self, instance) that controls the fetching of the object from the database:
from datetime import datetime as dt
from collections import OrderedDict
from rest_framework.relations import PKOnlyObject
from rest_framework.fields import SkipField, empty
def __init__(
self, instance=None, data=empty, asTime=str(dt.now()), **kwargs):
self.asTime = asTime
self.instance = instance
if data is not empty:
self.initial_data = data
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {})
kwargs.pop('many', None)
super().__init__(**kwargs)
def to_representation(self, instance):
# substitute instance with my raw query
# WARNING: self.asTime is a custom variable, check the
# __init__ method above!
instance = Car.objects.raw(
'''
select * from db_car
for system_time as of timestamp %s
where id=%s;
''', [self.asTime, instance.id])[0]
ret = OrderedDict()
fields = self._readable_fields
for field in fields:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
check_for_none = attribute.pk if isinstance(
attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
return ret
You can find the original code here: https://github.com/encode/django-rest-framework/blob/19655edbf782aa1fbdd7f8cd56ff9e0b7786ad3c/rest_framework/serializers.py#L335
Then finally in the DriverSerializer class:
class DriverSerializer(serializers.ModelSerializer):
car = CarSerializer(asTime='2021-02-05 14:34:00')
class Meta:
model = Driver
fields = '__all__'
I am trying to use cached_property and classmethod decorators together in a viewset but it doesnt work regardless of their mutual position. Is it any chance to make it work together or cached_property doesnt work with classmethod?
Tnanks.
#cached_property
#classmethod
def get_child_extra_actions(cls):
"""
Returns only extra actions defined in this exact viewset exclude actions defined in superclasses.
"""
all_extra_actions = cls.get_extra_actions()
parent_extra_actions = cls.__base__.get_extra_actions()
child_extra_actions = set(all_extra_actions).difference(parent_extra_actions)
return (act.__name__ for act in child_extra_actions)
For cached property with classmethod usage i wrote that code a few days ago:
from django.utils.decorators import classproperty
class cached_classproperty(classproperty):
def get_result_field_name(self):
return self.fget.__name__ + "_property_result" if self.fget else None
def __get__(self, instance, cls=None):
result_field_name = self.get_result_field_name()
if hasattr(cls, result_field_name):
return getattr(cls, result_field_name)
if not cls or not result_field_name:
return self.fget(cls)
setattr(cls, result_field_name, self.fget(cls))
return getattr(cls, result_field_name)
It will be caching result in class-level.
Usage is similar as classproperty:
#cached_classproperty
def some_func(cls, *args, **kwargs):
...
If you do not have django in dependencies, then you may want prevent classproperty parent usage (sources). In that case you may use that decorator:
class cached_classproperty(classproperty):
def __init__(self, method=None):
self.fget = method
def get_result_field_name(self):
return self.fget.__name__ + "_property_result" if self.fget else None
def __get__(self, instance, cls=None):
result_field_name = self.get_result_field_name()
if hasattr(cls, result_field_name):
return getattr(cls, result_field_name)
if not cls or not result_field_name:
return self.fget(cls)
setattr(cls, result_field_name, self.fget(cls))
return getattr(cls, result_field_name)
def getter(self, method):
self.fget = method
return self
What I am trying to do is best described by example. Consider the following:
class QuerySetManager(models.Manager):
def get_queryset(self):
result = self.model.QuerySet(self.model)
try:
result = result.filter(is_deleted=False)
except FieldError:
pass
return result
class MyModel(model.Models):
# core fields
objects = QuerySetManager()
class Meta:
managed = False
db_table = 'my_model'
class QuerySet(QuerySet):
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
# call custom update
In effect, I am trying to override the update method of QuerySet's superclass. If something special happens, I would like to implement update process myself, and otherwise - call the standard update method of the superclass.
Any help on what the correct syntax is?
UPDATE
Let me provide a bit detailed background.
I am using the pattern found here.
The entire architecture looks like this:
class DeleteMixin(models.Model):
is_deleted = models.BooleanField(default=False)
class Meta:
abstract = True
class QuerySetManager(models.Manager):
def get_queryset(self):
result = self.model.QuerySet(self.model)
try:
result = result.filter(is_deleted=False)
except FieldError:
pass
return result
class Sms(DeleteMixin):
# core fields
objects = QuerySetManager()
class Meta:
managed = False
db_table = 'sms'
class QuerySet(QuerySet):
def inbox(self, user):
return self.filter(sms_type_id = 1)
def outbox(self, user):
return self.filter(sms_type_id = 2)
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
# the issue in question - call custom update
This architecture enables me to:
exclude records with is_deleted field equal to 1 (for those tables where there is such field)
use chainable filters like sms.objects.inbox().outbox()
It's not enitrely clear what you are asking. IF you are trying to add a custom update method to a Queryset's superclass update method like this:
class MyQuerySet(QuerySet):
def update(self, *args, **kwargs):
....
super(MyQuerySet,self).update(*args,**kwargs)
if you are trying to override the save method in the model
class MyModel(model.Models):
def save(self,*args,**kwargs):
...
super(MyModel,self).save(*args,**kwargs)
Update (pun intended)
The code sample I have posted above is correct. The QuerySet class definitely as an update method. as can be seen here:
https://github.com/django/django/blob/master/django/db/models/query.py#L630
And if you have sub classed QuerySet correctly you will not get the error. But you haven't
class QuerySet(QuerySet):
This is incorrect.
Eventually, I got it work this way:
def update(self, *args, **kwargs):
if something_special:
# handle that special case
else:
super(self.model.QuerySet, self).update(*args,**kwargs)
What is the proper way to retrieve the model class dynamically in a CBV?
I realize I have to use apps.get_model, but not sure where to do that.
I would like to make my delete (and other) views more "DRY".
class DeleteParamView(generic.DeleteView):
# The following does not work since kwargs cannot be accessed
#model = apps.get_model('patients', 'param' + self.kwargs['param_name'])
def __init__(self, *args, **kwargs):
from django.apps import apps
self.model = apps.get_model('persons', 'param' + self.kwargs['param_name'])
super(DeleteParamView, self).__init__(*args, **kwargs)
Unfortunately self.kwargs cannot be accessed yet; at least I get 'DeleteParamView' object has no attribute 'kwargs'
I also tried to override def get_model() but that does not exist as part of the CBV.
Override the get_queryset method.
def get_queryset(self):
Model = apps.get_model('persons', 'param' + self.kwargs['param_name'])
return Model.objects.all()
I have a simple model MyModel with a date field named publication_date. I also have a custom manager that filters my model based on this date field.
This custom manager is accessible by .published and the default one by .objects.
from datetime import date, datetime
from django.db import models
class MyModelManager(models.Manager):
def get_query_set(self):
q = super(MyModelManager, self).get_query_set()
return q.filter(publication_date__lte=datetime.now())
class MyModel(models.Model):
...
publication_date = models.DateField(default=date.today())
objects = models.Manager()
published = MyModelManager()
This way, I got access to all objects in the admin but only to published ones in my views (using MyModel.published.all() queryset).
I also have
def get_previous(self):
return self.get_previous_by_publication_date()
def get_next(self):
return self.get_next_by_publication_date()
which I use in my templates: when viewing an object I can link to the previous and next object using
{{ object.get_previous }}
The problem is: this returns the previous object in the default queryset (objects) and not in my custom one (published).
I wonder how I can do to tell to this basic model functions (get_previous_by_FOO) to use my custom manager.
Or, if it's not possible, how to do the same thing with another solution.
Thanks in advance for any advice.
Edit
The view is called this way in my urlconf, using object_detail from the generic views.
(r'^(?P<slug>[\w-]+)$', object_detail,
{
'queryset': MyModel.published.all(),
'slug_field': 'slug',
},
'mymodel-detail'
),
I'm using Django 1.2.
In fact, get_next_or_previous_by_FIELD() Django function (which is used by get_previous_by_publication_date...) uses the default_manager.
So I have adapted it to reimplement my own utility function
def _own_get_next_or_previous_by_FIELD(self, field, is_next):
if not self.pk:
raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
op = is_next and 'gt' or 'lt'
order = not is_next and '-' or ''
param = smart_str(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = MyModel.published.filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
return qs[0]
except IndexError:
def get_previous(self):
return self._own_get_next_or_previous_by_FIELD(MyModel._meta.fields[4], False)
def get_next(self):
return self._own_get_next_or_previous_by_FIELD(MyModel._meta.fields[4], True)
This is not a very clean solution, as I need to hardcode the queryset and the field used, but at least it works.