Django CBV get list, get object - python

I've got a simple CBV for all CRUD operations on my model (let's say Book). How should I implement getting object by id and getting a list of all objects? Seems there are many options like:
Just create two separate classes Book and BookList.
Write some kind of dispatcher inside get method.
class BookView(View):
def get(self, request, *args, **kwargs):
if 'id' in self.kwargs:
self.get_object(request, *args, **kwargs)
else:
self.get_list(request, *args, **kwargs)
Override View.dispatch method so that it will call get_list() method if no id provided.
etc...
So what is best way?

Best way for crud API's is to use DRF and subclass the generics.ListCreateAPIView to get a list of all objects or create new object.
generics.RetrieveUpdateDestroyAPIView to get an object by id or to destroy and update the object by id. Like this:
class BookDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Retrieve, update or delete a book instance.
"""
queryset = Book.objects.all()
serializer_class = BookSerializer
You will probably have to create your serializers accordingly.

Related

on_save() is not triggered when model is created

I got the two following models:
class Stuff(models.Model):
...
def custom_function(self):
...
class MyModel(models.Model):
name = models.CharField(max_length=200)
many_stuff = models.ManyToManyField(Stuff, related_name="many_stuff+")
many_other_stuff = models.ManyToManyField(Stuff, related_name="many_other_stuff+")
def __init__(self, *args, **kwargs):
super(MyModel, self).__init__(*args, **kwargs)
for stuff in self.many_stuff.all():
many_stuff.custom_function()
def on_save(self, *args, **kwargs):
for stuff in self.many_stuff.all():
many_stuff.custom_function()
super(MyModel, self).save(*args, **kwargs)
When I create a new instance of MyModel through the Django Admin, I want to execute for each Stuff object, a function. It works on_save but I cannot make it work on __init__ for some reason. If I create the new instance, the functions don't get executed. If I save the newly created instance, the functions do get executed.
To be more specific, when I use this __init__ method, the model breaks on instance creation with the error:
MyModel needs to have a value for field "id" before this many-to-many relationship can be used.
I also attempted doing the same thing with the post_save signal, instead of using __init__ and on_save but I had the same problem. On object creation, the function custom_function did not get executed, but it did when I saved the object after it has been created by clicking on the save button.
Any ideas?
You simply cant create M2M join while creating object.
Because M2M model fields don't have real db fields, but have intermediary table that stores id from both model. When you create relation between two objects, both objects must have an id value ready to use.
First create MyModel instance without M2M fields, than set M2M fields. Something like that;
newModel = MyModel.objects.create(name=someting)
newStuff = Stuff.objects.create(..)
newModel.many_stuff.add(newStuff)

Django Rest Framework filter class method with two parameters

I have a model which relates to many models, like this:
class Father:
son = # Foreign key to Son model
class Son:
#property
def son_daughters:
if ... :
obj = TypeA.objects.get(...)
elif ... :
obj = TypeB.objects.get(...)
else:
obj = TypeC.objects.get(...)
return obj
I would like to get Father data from daughter name or type. I have this filter class where I need to send two query set parameters related to daughter in order to get daughter ids and apply it as a filter to Father. This is my filter class:
class FatherFilter(django_filters.rest_framework.FilterSet):
def daughter(self, method_name, args, **kwargs):
print(method_name, args, kwargs)
...
daughter_id = django_filters.NumberFilter(method=daughter)
But when I call this endpoint I just get one query parameter and not all.
Is there a way to get the query parameters inside this method instead of just one?
Thanks in advance.
In order to achieve this, I found that Django Rest Framework has a class that extends from django_filters. This class is called BaseFilterBackend and can be used to extend the default backend for filtering any request. So what I did was adding a class extended from BaseFilterBackend like this:
from rest_framework import filters
class FatherFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
daughter_id = request.query_params.get("daughter_id", None)
daughter_name = request.query_params.get("daughter_name", None)
if daughter_id and daughter_name:
kwargs = {
daughter_name: daughter_id
}
queryset = queryset.filter(**kwargs)
return queryset
This filter will apply before others, so even if you are using a FilterSet class, you will not lose the filters from your BaseFilterBackend. The problem with this solution is that relies in rest_framework.filters package, a filter that its not related to django_filters.
This might not be the best way to achieve this, so if you have better ideas please add them to help others with a similar problem.

Use decorated methods (#property) in datatable's fields array (django-eztables)

I'm using django-eztables app (0.3.2), and I'm trying to use some model's properties within fields:
model's class:
class AModel(models.Model):
...
#property
def a_property(self):
return 'something'
eztables' view:
class AView(DatatablesView):
model = AModel
fields = [
...,
"a_property",
]
According to what I saw, this cannot work because method process_dt_response in eztables' DatatablesView class uses .values(*self.get_db_fields()) on the queryset.
The only way to do this is that I found is to overwrite mentioned method within my AView class, and just remove mentioned values call:
def process_dt_response(self, data):
self.form = DatatablesForm(data)
print self.form
if self.form.is_valid():
self.object_list = self.get_queryset()
return self.render_to_response(self.form)
else:
return HttpResponseBadRequest()
Is this ok (will it have some other bad side-effects)? Is there any other, better way to do this?
One strategy that worked for me was to append additional data just before it was returned to the client. render_to_response() calls get_row() to get the final array (for each record). Override get_row() and append custom data. One assumption here though is that all the data required to get/generate the additional custom data is available in get_row().

Django Rest Framework dynamic nested serialization

I have a model, Parent which contains a Django ContentType GenericForeignKey relationship to various child models (ChildA, ChildB) in Parent.child
I'm trying to get ListCreateAPIView and other listing views working with this setup. Originally I handled the serialization of the child instance using a SerializerMethodField which looked something like:
class ParentSerializer(serializers.ModelSerializer):
child = serializers.SerializerMethodField('get_child')
(other fields)
def get_child(self, obj):
if obj.content_type == "child_a":
return ChildASerializer(obj.child).data
...
Now I want to take advantage of Django Rest Framework to its full (including deserialization/creation/validation) so I want to avoid my current approach and increase DRYness by doing:
class ParentSerializer(serializers.ModelSerializer):
(code for serializing parent fields except 'child' attribute)
class ChildASerializer(serializers.ModelSerializer):
(code for ChildA fields)
class ParentTypeASerializer(ParentSerializer):
child = ChildASerializer()
If i'm reading the docs right, this means POST/PUT will go through the serializer's process without me having to override the post methods in views and other uglyness. This is important as ChildA,ChildB,ChildC come from plugins and the core Parent/ParentSerializer should be as unaware of them as possible.
My thinking was to override get_serializer() in the view, but when listing many objects, I don't see how I can provide ParentTypeASerializer, ParentTypeBSerializer etc in the view.
def get_serializer(self, instance=None, data=None, files=None, many=False, partial=True):
serializer_class = None
if instance and instance.content_type == "child_a":
serializer_class = ParentTypeASerializer
if instance and instance.content_type == "child_b":
serializer_class = ParentTypeBSerializer
...
# What about many=True ?!
return serializer_class(instance,data=data,files=files,many-many,partial=partial,context=context)
Another idea I had was to write a PolymorphicField class extending WritableField that does the decision. Unsure if this is the simplest approach:
class ParentSerializer(serializers.ModelSerializer):
child = PolymorphicChildSerializerProxy() # Passes through/wraps the right serializer
question: is there any dynamic/runtime/per-object way to provide the right serializer for an Generic/polymorphic nested object in such a way either at the view level or the parent serializer? Ideally something like the second example and an override in the view that works for List/Create/Destroy generics or like the first example except I return a serializer class rather than serialized data?

django form custom widget for a "ListField" and displaying a list [duplicate]

I'm experimenting with django-nonrel on appengine and trying to use a djangotoolbox.fields.ListField to implement a many-to-many relation. As I read in the documentation a ListField is something that you can use to make a workaround for djamgo-nonrel not supporting many-to-many relations.
This is an excerpt from my model:
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
So if I am getting this right I am creating a list of foreign keys to another class to show a relationship with multiple instances of another class
With this approach everything works fine ... No Exceptions. I can create `MyClass' objects in code and views. But when I try to use the admin interface I get the following error
No form field implemented for <class 'djangotoolbox.fields.ListField'>
So I though I would try something that I haven't done before. Create my own field. Well actually my own form for editing MyClass instances in the admin interface. Here is what I did:
class MyClassForm(ModelForm):
field = fields.MultipleChoiceField(choices=AnotherClass.objects.all(), widget=FilteredSelectMultiple("verbose_name", is_stacked=False))
class Meta:
model = MyClass
then I pass MyClassForm as the form to use to the admin interface
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
admin.site.register(MyClass, MyClassAdmin)
I though that this would work but It doesn't. When I go to the admin interface I get the same error as before. Can anyone tell what I am doing wrong here ... or if you have any other suggestions or success stories of using the ListField, SetField, etc. from djangotoolbox.fields in the admin interface it would be very much appreciated.
OK, here is what I did to get this all working ...
I'll start from the beginning
This is what what my model looked like
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
I wanted to be able to use the admin interface to create/edit instances of this model using a multiple select widget for the list field. Therefore, I created some custom classes as follows
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
These classes allow the listfield to be used in the admin. Then I created a form to use in the admin site
class MyClassForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyClasstForm,self).__init__(*args, **kwargs)
self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
if self.instance.pk:
self.fields['field'].initial = self.instance.field
class Meta:
model = MyClass
After having done this I created a admin model and registered it with the admin site
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
def __init__(self, model, admin_site):
super(MyClassAdmin,self).__init__(model, admin_site)
admin.site.register(MyClass, MyClassAdmin)
This is now working in my code. Keep in mind that this approach might not at all be well suited for google_appengine as I am not very adept at how it works and it might create inefficient queries an such.
As far as I understand, you're trying to have a M2M relationship in django-nonrel, which is not an out-of-the-box functionality. For starters, if you want a quick hack, you can go with this simple class and use a CharField to enter foreign keys manually:
class ListFormField(forms.Field):
""" A form field for being able to display a djangotoolbox.fields.ListField. """
widget = ListWidget
def clean(self, value):
return [v.strip() for v in value.split(',') if len(v.strip()) > 0]
But if you want to have a multiple selection from a list of models normally you'd have to use ModelMultipleChoiceField, which is also not functional in django-nonrel. Here's what I've done to emulate a M2M relationship using a MultipleSelectField:
Let's say you have a M2M relationship between 2 classes, SomeClass and AnotherClass respectively. You want to select the relationship on the form for SomeClass. Also I assume you want to hold the references as a ListField in SomeClass. (Naturally you want to create M2M relationships as they're explained here, to prevent exploding indexes if you're working on App Engine).
So you have your models like:
class SomeClass(models.Model):
another_class_ids = ListField(models.PositiveIntegerField(), null=True, blank=True)
#fields go here
class AnotherClass(models.Model):
#fields go here
And in your form:
class SomeClassForm(forms.ModelForm):
#Empty field, will be populated after form is initialized
#Otherwise selection list is not refreshed after new entities are created.
another_class = forms.MultipleChoiceField(required=False)
def __init__(self, *args, **kwargs):
super(SomeClassForm,self).__init__(*args, **kwargs)
self.fields['another_class'].choices = [(item.pk,item) for item in AnotherClass.objects.all()]
if self.instance.pk: #If class is saved, highlight the instances that are related
self.fields['another_class'].initial = self.instance.another_class_ids
def save(self, *args, **kwargs):
self.instance.another_class_ids = self.cleaned_data['another_class']
return super(SomeClassForm, self).save()
class Meta:
model = SomeClass
Hopefully this should get you going for the start, I implemented this functionality for normal forms, adjust it for admin panel shouldn't be that hard.
This could be unrelated but for the admin interface, be sure you have djangotoolbox listed after django.contrib.admin in the settings.. INSTALLED_APPS
You could avoid a custom form class for such usage by inquiring for the model object
class ModelListField(ListField):
def __init__(self, embedded_model=None, *args, **kwargs):
super(ModelListField, self).__init__(*args, **kwargs)
self._model = embedded_model.embedded_model
def formfield(self, **kwargs):
return FormListField(model=self._model, **kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
widget = ListFieldWidget
def __init__(self, model=None, *args, **kwargs):
self._model = model
super(FormListField, self).__init__(*args, **kwargs)
self.widget.choices = [(unicode(i.pk), i) for i in self._model.objects.all()]
def to_python(self, value):
return [self._model.objects.get(pk=key) for key in value]
def clean(self, value):
return value

Categories

Resources