class TimeStampedModel(models.Model):
created = DateTimeField(_('created'), auto_now=False, auto_now_add=True)
modified = DateTimeField(_('modified'), auto_now=True, auto_now_add=False)
class Meta:
abstract = True
class TimeFramedModel(models.Model):
start = models.DateTimeField(_('start'), null=True, blank=True)
end = models.DateTimeField(_('end'), null=True, blank=True)
class Meta:
abstract = True
class EntryQueryset(QuerySet):
def published(self):
return self.filter(self.status == 'published')
class EntryManger(models.Manager):
def get_query_set(self):
print 'using right custom manager'
return EntryQueryset(self.model, using=self._db)
class Entry(TimeStampedModel, TimeFramedModel):
status = models.CharField(
_('status'), choices=STATUS_CHOICES, max_length=16,
default='draft')
objects = EntryManger()
When I do Entry.objects.published(), the error raises. It complains that
'EntryManger' object has no attribute 'published',
The text of 'using right custom manager' wasn't printed.
What could cause this error? Thank you!
Entry.objects is a manager, not a queryset. You put published inside your custom queryset, so you need to do Entry.objects.all().published() or Entry.objects.get_queryset().published()
You also aren't implementing published correctly. It should be more like:
class EntryQueryset(QuerySet):
def published(self):
return self.filter(status='published')
Your method published() should be on the EntryManager class, there is no need to create a new QuerySet class (generally that is only when you want to dig deeper into how data is fetched from the database).
The correct solution would be:
class EntryManger(models.Manager):
def published(self):
print 'using right custom manager'
return self.filter(self.status == 'published')
You can delete the EntryQuerySet class entirely.
Related
I have a model class in Django which has a ForeignKey referencing the model it actually belongs to:
class Foo(models.Model):
name = models.CharField(max_length=256, verbose_name="Name")
#... some other fields
bar = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True
)
def __str__(self):
return self.name
I want to add a custom method in that class which resolves, on the fly, the name in a new field, e.g. bar_resolved when instantiating it in a QuerySet in a view:
from .models import Foo
foo = Foo.objects.all()
# do stuff
I've tried this:
class Foo(models.Model):
name = models.CharField(max_length=256, verbose_name="Name")
#... some other fields
bar = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True
)
# preparing the resolved bar field which should contain the 'name' value corresponding to the id:
bar_resolved = models.CharField(
max_length=256,
verbose_name="Bar name resolved",
null=True
)
def __str__(self):
return self.name
def resolve(self):
if self.bar:
self.bar_resolved = self.bar.name
return super(Foo, self).resolve()
Then in my view:
from .models import Foo
foo = Foo.objects.all()
foo.resolve()
but it raises: 'QuerySet' object has no attribute 'resolve'
How could I achieve that? and do I need to hard code a 'resolved' field in my model for that (I think it's overkill to do so)?
I do not understand why would you have a Foreing key referencing self in the database.
Instead of using resolve, you could probably do it on the save long before - i.e. when setting value of "bar"
Another idea that comes to mind is setting it in the __ init__
method of the model link to Stack
hope this helps.
def save(self, force_insert: bool = False, force_update: bool = False) -> None:
if self.field is None:
self.field = "value"
# and so on...
return super().save(force_insert, force_update)
One way is to annotate..[Django-doc] your queryset using F expressions..[Django-doc] with bar's name field:
from django.db.models import F
foos = Foo.objects.annotate(bar_resolved=F("bar__name")).all()
for foo in foos:
print(foo.bar_resolved)
Problem:
Dash signs display in 'Parent category' column before I open any menu item. I get 'Parent category' items when open menu item profile(see admin.py below)
Before and after opening of menu profile: picture1 and picture2 (pay attention on Parent Category column)
My models.py: (attention on parent_id)
class Menu(models.Model):
cat_title = models.CharField(max_length=150, verbose_name='Category title')
menu_title = models.CharField(max_length=150, verbose_name='Menu title')
parent_id = models.IntegerField(blank=True, null=True, verbose_name='Parent category', choices=(('',''),))
url = models.CharField(max_length=255, verbose_name='URL', blank=True)
named_url = models.CharField(max_length=255, verbose_name='Named URL', blank=True)
level = models.IntegerField(default=0, editable=False)
My admin.py: ()
class MyMenu(admin.ModelAdmin):
def get_choices(self):
choices = (('',''),)
categories = models.Menu.objects.all().values()
for i in categories:
choices += ((i['id'], i['cat_title']),)
return choices
def formfield_for_choice_field(self, db_field, request):
if db_field.name == 'parent_id':
db_field.choices = self.get_choices()
return super().formfield_for_choice_field(db_field, request)
list_display = ('cat_title', 'menu_title', 'parent_id', 'level')
list_display_links = ('cat_title', 'menu_title')
admin.site.register(models.Menu, MyMenu)
Question: How could I rewrite my admin.py to show parent_id items without opening any menu item profile?
I've already tried Model.get_FOO_display() but it doesn't work in right way. Any help would be appreciated.
Without changing your model, the simplest solution is probably to add a parent method in your admin and use it instead of "parent_id" in the list_display list:
class MyMenu(admin.ModelAdmin):
# ....
def parent(self, obj):
if obj.parent_id:
return Menu.objects.get(pk=obj.parent_id).cat_title
return ""
list_display = ('cat_title', 'menu_title', 'parent', 'level')
# Unrelated but you may also want to rewrite `get_choices`
# in a simpler and more performant way:
def get_choices(self):
choices = models.Menu.objects.values_list("id", "cat_title"))
return (('',''),) + tuple(choices)
Or to make parent a method or property on your Menu model:
class Menu(models.Model):
# ...
# you may want to use django's `cached_property` instead
# but then you'll have to invalidate the cache when setting
# (or unsetting) `.parent_id`
#property
def parent(self):
if not self.parent_id:
return None
return Menu.objects.get(pk=self.parent_id)
and add "parent" to your admin's list_display.
BUT since Menu.parent_id is actually a foreign key on Menu, the proper solution is to declare it as such in your model:
class Menu(models.Model):
cat_title = models.CharField(max_length=150, verbose_name='Category title')
menu_title = models.CharField(max_length=150, verbose_name='Menu title')
parent = models.ForeignKey("self", blank=True, null=True, related_name="children")
# etc
I have app in Django 1.8 and I want to take last object (based on pub_date) and set for this object filed is_mainteaser on True and rest ssould be set on False.
Here is my code, but latest object hasn't field set to True.
class ArticleListView(ListView):
model = Article
queryset = Article.objects.order_by('-pub_date')
def get_context_data(self, **kwargs):
context = super(ArticleListView, self).get_context_data(**kwargs)
lates_object = Article.objects.latest('pub_date')
lates_object.is_mainteaser = True
return context
Here is my model:
class Article(model.Models):
title = models.CharField(max_length=255)
short_text = models.TextField(max_length=10000, default='')
image = FilerImageField(null=True)
pub_date = models.DateTimeField('date published')
online_from = models.DateTimeField('online from', blank=True)
online_to = models.DateTimeField('online to', blank=True)
position = models.PositiveIntegerField(default=0)
is_mainteaser = models.BooleanField(default=False)
def __str__(self):
return self.title
class Meta:
ordering = ['position']
When you have object instance and change model attribute you must save instance. Example:
lates_object = Article.objects.latest('pub_date')
lates_object.is_mainteaser = True
lates_object.save()
I think better for this solution is use django signals or action when you add new article. In ListView is't good solution to do it that.
I have a linked model:
class Children(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
child_name = models.CharField(max_length=150, null=True, blank=True)
slug = AutoSlugField(populate_from='child_name')
blood_group = models.CharField(max_length=5, blank=True)
class Meta:
unique_together = ('slug', 'person')
def get_absolute_url(self):
return self.person.get_absolute_url()
def get_delete_url(self):
return reverse(
'member:children-delete',
kwargs={
'person_slug': self.person.slug,
'children_slug': self.slug})
def get_update_url(self):
return reverse(
'member:children-update',
kwargs={
'person_slug': self.person.slug,
'children_slug': self.slug})
my forms.py:
class ChildrenForm( SlugCleanMixin, forms.ModelForm):
class Meta:
model = Children
exclude = ('person',)
def clean(self):
cleaned_data = super().clean()
slug = cleaned_data.get('slug')
person_obj = self.data.get('person')
exists = (
Children.objects.filter(
slug__iexact=slug,
person=person_obj,
).exists())
if exists:
raise ValidationError(
"Children with this Slug "
"and Person already exists.")
else:
return cleaned_data
def save(self, **kwargs):
instance = super().save(commit=False)
instance.person = (
self.data.get('person'))
instance.save()
self.save_m2m()
return instance
views.py:
class ChildrenCreate( ChildrenFormMixin, ChildrenGetObjectMixin,
PersonContextMixin,CreateView):
template_name = 'member/children_form.html'
model = Children
form_class = ChildrenForm
class ChildrenUpdate(ChildrenFormMixin, ChildrenGetObjectMixin,
PersonContextMixin,UpdateView):
template_name = 'member/children_form.html'
model = Children
form_class = ChildrenForm
slug_url_kwarg = 'children_slug'
class ChildrenDelete(ChildrenFormMixin,ChildrenGetObjectMixin,
PersonContextMixin,DeleteView):
model = Children
slug_url_kwarg = 'children_slug'
def get_success_url(self):
return (self.object.person
.get_absolute_url())
my utils.py:
class ChildrenFormMixin():
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
if self.request.method in ('POST', 'PUT'):
self.person = get_object_or_404(
Person,
slug__iexact=self.kwargs.get(
self.person_slug_url_kwarg))
data = kwargs['data'].copy()
data.update({'person': self.person})
kwargs['data'] = data
return kwargs
class ChildrenGetObjectMixin():
def get_object(self, queryset=None):
person_slug = self.kwargs.get(
self.person_slug_url_kwarg)
children_slug = self.kwargs.get(
self.slug_url_kwarg)
return get_object_or_404(
Children,
slug__iexact=children_slug,
person__slug__iexact=person_slug)
class PersonContextMixin():
person_slug_url_kwarg = 'person_slug'
person_context_object_name = 'person'
def get_context_data(self, **kwargs):
person_slug = self.kwargs.get(
self.person_slug_url_kwarg)
person = get_object_or_404(
Person, slug__iexact=person_slug)
context = {
self.person_context_object_name:
person,
}
context.update(kwargs)
return super().get_context_data(**context)
The children created more than one for same name of same parents. When I tried to edit children it gives "get() returned more than one Children -- it returned 2!" error. In traceback, it said, 'person__slug__iexact=person_slug' is the direct causes of this traceback.
In the form, I added clean method to catch the error and maintain uniqueness of children name of same parents but it not worked. Could I get suggestions where I do wrong?
Edit:
my Person model:
class Person(models.Model):
name = models.CharField(max_length=250)
slug = AutoSlugField(populate_from='name')
birth_date = models.DateField(null=True, blank=True)
blood_group = models.CharField(max_length=5)
present_address = models.CharField(max_length=250, blank=True)
permanent_address = models.CharField(max_length=250, blank=True)
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
related_name='member_persons')
class Meta:
ordering = ['name']
unique_together = ['name', 'birth_date']
I believe you are using AutoSlugField from django-autoslug, and you trying to get by non-unique field. AutoSlugField won't make your field unique by default, from docs:
AutoSlugField can also perform the following tasks on save:
populate itself from another field (using populate_from),
use custom slugify function (using slugify or Settings), and
preserve uniqueness of the value (using unique or unique_with).
None of the tasks is mandatory, i.e. you can have auto-populated non-unique fields, manually entered unique ones (absolutely unique or within a given date) or both.
So quick fix would be slug = AutoSlugField(populate_from='child_name', unique=True)
UPDATE(Since you posted your Person model)
The problem is the same and solution is the same.
Explanation:
For example you have two Person objects:
id name slug birth_date
1 alex alex 10.10.2016
2 alex alex 10.10.2015
This won't violate unique_together = ['name', 'birth_date']
And you got two Children objects:
id name slug person_id
1 john john 1
2 john john 2
And that won't violate unique_together = ('slug', 'person') neither
Then you are making query
get_object_or_404(
Children,
slug__iexact='john',
person__slug__iexact='alex')
Which would match two objects. So you got problem. Quick fix would be to make slug unique=True.
I can't find this info in the docs or on the interwebs.
latest django-rest-framework, django 1.6.5
How does one create a ModelSerializer that can handle a nested serializers where the nested model is implemented using multitable inheritance?
e.g.
######## MODELS
class OtherModel(models.Model):
stuff = models.CharField(max_length=255)
class MyBaseModel(models.Model):
whaddup = models.CharField(max_length=255)
other_model = models.ForeignKey(OtherModel)
class ModelA(MyBaseModel):
attr_a = models.CharField(max_length=255)
class ModelB(MyBaseModel):
attr_b = models.CharField(max_length=255)
####### SERIALIZERS
class MyBaseModelSerializer(serializers.ModelSerializer):
class Meta:
model=MyBaseModel
class OtherModelSerializer(serializer.ModelSerializer):
mybasemodel_set = MyBaseModelSerializer(many=True)
class Meta:
model = OtherModel
This obviously doesn't work but illustrates what i'm trying to do here.
In OtherModelSerializer, I'd like mybasemodel_set to serialize specific represenntations of either ModelA or ModelB depending on what we have.
If it matters, I'm also using django.model_utils and inheritencemanager so i can retrieve a queryset where each instance is already an instance of appropriate subclass.
Thanks
I've solved this issue a slightly different way.
Using:
DRF 3.5.x
django-model-utils 2.5.x
My models.py look like this:
class Person(models.Model):
first_name = models.CharField(max_length=40, blank=False, null=False)
middle_name = models.CharField(max_length=80, blank=True, null=True)
last_name = models.CharField(max_length=80, blank=False, null=False)
family = models.ForeignKey(Family, blank=True, null=True)
class Clergy(Person):
category = models.IntegerField(choices=CATEGORY, blank=True, null=True)
external = models.NullBooleanField(default=False, null=True)
clergy_status = models.ForeignKey(ClergyStatus, related_name="%(class)s_status", blank=True, null=True)
class Religious(Person):
religious_order = models.ForeignKey(ReligiousOrder, blank=True, null=True)
major_superior = models.ForeignKey(Person, blank=True, null=True, related_name="%(class)s_superior")
class ReligiousOrder(models.Model):
name = models.CharField(max_length=255, blank=False, null=False)
initials = models.CharField(max_length=20, blank=False, null=False)
class ClergyStatus(models.Model):
display_name = models.CharField(max_length=255, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
Basically - The base model is the "Person" model - and a person can either be Clergy, Religious, or neither and simply be a "Person". While the models that inherit Person have special relationships as well.
In my views.py I utilize a mixin to "inject" the subclasses into the queryset like so:
class PersonSubClassFieldsMixin(object):
def get_queryset(self):
return Person.objects.select_subclasses()
class RetrievePersonAPIView(PersonSubClassFieldsMixin, generics.RetrieveDestroyAPIView):
serializer_class = PersonListSerializer
...
And then real "unDRY" part comes in serializers.py where I declare the "base" PersonListSerializer, but override the to_representation method to return special serailzers based on the instance type like so:
class PersonListSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
if isinstance(instance, Clergy):
return ClergySerializer(instance=instance).data
elif isinstance(instance, Religious):
return ReligiousSerializer(instance=instance).data
else:
return LaySerializer(instance=instance).data
class Meta:
model = Person
fields = '__all__'
class ReligiousSerializer(serializers.ModelSerializer):
class Meta:
model = Religious
fields = '__all__'
depth = 2
class LaySerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
class ClergySerializer(serializers.ModelSerializer):
class Meta:
model = Clergy
fields = '__all__'
depth = 2
The "switch" happens in the to_representation method of the main serializer (PersonListSerializer). It looks at the instance type, and then "injects" the needed serializer. Since Clergy, Religious are all inherited from Person getting back a Person that is also a Clergy member, returns all the Person fields and all the Clergy fields. Same goes for Religious. And if the Person is neither Clergy or Religious - the base model fields are only returned.
Not sure if this is the proper approach - but it seems very flexible, and fits my usecase. Note that I save/update/create Person thru different views/serializers - so I don't have to worry about that with this type of setup.
I was able to do this by creating a custom relatedfield
class MyBaseModelField(serializers.RelatedField):
def to_native(self, value):
if isinstance(value, ModelA):
a_s = ModelASerializer(instance=value)
return a_s.data
if isinstance(value, ModelB):
b_s = ModelBSerializer(instance=value)
return b_s.data
raise NotImplementedError
class OtherModelSerializer(serializer.ModelSerializer):
mybasemodel_set = MyBaseModelField(many=True)
class Meta:
model = OtherModel
fields = # make sure we manually include the reverse relation (mybasemodel_set, )
I do have concerns that instanting a Serializer for each object is the reverse relation queryset is expensive so I'm wondering if there is a better way to do this.
Another approach i tried was dynamically changing the model field on MyBaseModelSerializer inside of __init__ but I ran into the issue described here:
django rest framework nested modelserializer
Using Django 3.1, I found that it is possible to override get_serializer instead of get_serializer_class, in which case you can access the instance as well as self.action and more.
By default get_serializer will call get_serializer_class, but this behavior can be adjusted to your needs.
This is cleaner and easier than the solutions proposed above, so I'm adding it to the thread.
Example:
class MySubclassViewSet(viewsets.ModelViewSet):
# add your normal fields and methods ...
def get_serializer(self, *args, **kwargs):
if self.action in ('list', 'destroy'):
return MyListSerializer(args[0], **kwargs)
if self.action in ('retrieve', ):
instance = args[0]
if instance.name.contains("really?"): # or check if instance of a certain Model...
return MyReallyCoolSerializer(instance)
else return MyNotCoolSerializer(instance)
# ...
return MyListSerializer(*args, **kwargs) # default
I'm attempting to use a solution that involves different serializer subclasses for the different model subclasses:
class MyBaseModelSerializer(serializers.ModelSerializer):
#staticmethod
def _get_alt_class(cls, args, kwargs):
if (cls != MyBaseModel):
# we're instantiating a subclass already, use that class
return cls
# < logic to choose an alternative class to use >
# in my case, I'm inspecting kwargs["data"] to make a decision
# alt_cls = SomeSubClass
return alt_cls
def __new__(cls, *args, **kwargs):
alt_cls = MyBaseModel.get_alt_class(cls, args, kwargs)
return super(MyBaseModel, alt_cls).__new__(alt_cls, *args, **kwargs)
class Meta:
model=MyBaseModel
class ModelASerializer(MyBaseModelSerializer):
class Meta:
model=ModelA
class ModelBSerializer(MyBaseModelSerializer):
class Meta:
model=ModelB
That is, when you try and instantiate an object of type MyBaseModelSerializer, you actually end up with an object of one of the subclasses, which serialize (and crucially for me, deserialize) correctly.
I've just started using this, so it's possible that there are problems I've not run into yet.
I found this post via Google trying to figure out how to handle multiple table inheritance without having to check the model instance type. I implemented my own solution.
I created a class factory and a mixin to generate the serializers for the child classes with the help of InheritanceManger from django-model-utils.
models.py
from django.db import models
from model_utils import InheritanceManager
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
# Use the InheritanceManager for select_subclasses()
objects = InheritanceManager()
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
serializers.py
from rest_framework import serializers
from .models import Location
def modelserializer_factory(model, class_name='ModelFactorySerializer',
meta_cls=None, **kwargs):
"""Generate a ModelSerializer based on Model"""
if meta_cls is None:
# Create a Meta class with the model passed
meta_cls = type('Meta', (object,), dict(model=model))
elif not hasattr(meta_cls, 'model'):
# If a meta_cls is provided but did not include a model,
# set it to the model passed into this function
meta_cls.model = model
# Create the ModelSerializer class with the Meta subclass
# we created above; also pass in any additional keyword
# arguments via kwargs
ModelFactorySerializer = type(class_name, (serializers.ModelSerializer,),
dict(Meta=meta_cls, **kwargs))
ModelFactorySerializer.__class__.__name__ = class_name
return ModelFactorySerializer
class InheritedModelSerializerMixin:
def to_representation(self, instance):
# Get the model of the instance
model = instance._meta.model
# Override the model with the inherited model
self.Meta.model = model
# Create the serializer via the modelserializer_factory
# This will use the name of the class this is mixed with.
serializer = modelserializer_factory(model, self.__class__.__name__,
meta_cls=self.Meta)
# Instantiate the Serializer class with the instance
# and return the data
return serializer(instance=instance).data
# Mix in the InheritedModelSerializerMixin
class LocationSerializer(InheritedModelSerializerMixin, serializers.ModelSerializer):
class Meta:
model = Location # 'model' is optional since it will use
# the instance's model
exclude = ('serves_pizza',) # everything else works as well
depth = 2 # including depth
views.py
from .models import Location
from .serializers import LocationSerializer
# Any view should work.
# This is an example using viewsets.ReadOnlyModelViewSet
# Everything else works as usual. You will need to chain
# ".select_subclasses()" to the queryset to select the
# child classes.
class LocationViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Location.objects.all().select_subclasses()
serializer_class = LocationSerializer