Django: When to put code in serializers.py or views.py - django

I am kind of confused about when should I put code in serializers.py or views.py.
My understanding is that serializers.py is where data is manipulated + converted between front and backend.
But so far, my understanding is that only the validation function and maybe a Meta class need to be in serializers.py. I feel like I can just manipulate the database directly in views.py. Like, I can just import the model in views.py and then do datModel.objects.create() or datModel.objects.get().someAttribute = somethingNew.

Since you mentioned Models and Views, my thinking is that the less logic about manipulating the model in the view the better, leave the view to focus on fetching the data, minimum data manipulation code via encapsulation, then present the data to the view
here is the overly simplified example
class Product(models.Model):
...
stock_level = model.Integer(..)
def reduce_stock_level(number)
# insert some 100 lines of validation
self.stock_level -= number
self.save()
In your view:
def checkout(...):
...
product = Product.object.get(...)
product.reduce_stock_level(1)
is cleaner than in the view to do the same thing
def checkout(...):
product = Product.object.get(...)
# insert 100 lines of validation
product.stock_level -= 1
product.save()
Also you can reuse the method in other places and seems more pythonic:
for product in Products.objects.all():
product.reduce_stock_level(10)

Related

Django REST Framework, display specific foreign key data objects

I'll get straight to my point
Here is my API output:
In pictures, I need to display objects that have primary_placeholder set to True.
I can't find answer on how to do it, I've tried searching in REST Api documentation but nothing seems to work, it always displays every single picture
serializers.py:
views.py
Models.py (Theese two that I'm currently working with)
Does anyone know what is the easiest and fastest way to do it? I have absolutely no idea and I've been trying figure it out for about 2 days now.
Database currently has no pictures with primary_placeholder set to True if anyone is confused
If want to show product pictures with primary_placeholder=True, we can use prefetch_related with Prefetch, please read this.
In your case above, i suggest:
views.py
from django.db import models
from your_app.models import Product, ProductPictures
class ProductList(APIView):
def get(self, request, format=None):
products = Product.objects.all().prefetch_related(
models.Prefetch(
"pictures",
queryset=ProductPictures.objects.filter(primary_placeholder=True)
)
)
serializer = ProductSerializer(products, many=True)
return Response(serializer.data)
Another suggest: In model ProductPictures field model, better if change the field name to product.
Good luck.
write a serializer method field and filter products in it.
class ProductSerializer(serializers.ModelSerializer):
filtered_pictures = serializers.MethodField()
class Meta:
model = Product
fields = ['brand','model','prize','filtered_pictures']
def get_filtered_pictures(self,obj):
if obj.pictures.exists():
queryset = obj.pictures.filter(primary_placeholder=True)
return ProductPictureSerializer(queryset, many=True).data
else:
return None

Django ModelForm hide field from form and use value from url

Thanks in advance for reading this. I can't wrap my head around it and it's getting quite frustrating by now.
We have the following registration form:
class RegistrationForm(forms.ModelForm):
class Meta:
model = Register
fields = ('name', 'company_name')
def clean(self):
if is not self.cleaned_data.get('card').is_available():
raise forms.ValidationError(_('Error'))
The Register model includes a card linked to a Card model. This includes is_available() which functionally works.
Our flow is:
The end user selects the card which lists all registrations for it.
They click the 'Add registration'-button which brings them to cards/{PK}/add.
The Add registration-button is a generic.View. In post(self, request, pk) I have the following code:
form = RegistrationForm(request.POST)
But how do I pass it the contents of Card.objects.get(pk=pk) to it?
I tried:
data = request.POST.copy()
data['card'] = pk
form = RegistrationForm(data)
But I think because card is not included in fields it gets lost somewhere, which makes sense from a sanitize-all-input-point of view, but I would very much like to add the card dynamically, in this case.
Any ideas?
So, just use CreateView and study how it does things using the linked site.
There is no need to use generic.View as it's the basic of basics. You only want to implement all this logic using generic.View to get more familiar with the way things work or if you need some very special form handling.
The short version would be:
from django.views import generic
from myapp.forms import RegistrationForm
class CardCreateView(generic.CreateView):
form_class = RegistrationForm
ModelForm has a save method. The correct way to solve this is to use it with commit=False, that will return an object that hasn’t yet been saved to the database. Then you can alter that object before finally saving it.
This is explained here in the docs
So this is what your code should look like:
form = RegistrationForm(request.POST)
form.save(commit=False)
form.card = Card.objects.get(pk=pk)
form.save_m2m()
save_m2m should be used if your model has many-to-many relationships with other models. In my case, it was a OneToOne, so I used save() instead.
If you use a CreateView instead of the generic View, the snippet above should go into your overridden form_valid method

Django admin form and inlined model with default value

I am updating a django app from 1.4 to 1.8 and have hit a small problem with django admin.
My models look like this
def new_key():
return binascii.b2a_hex(os.urandom(20))
class ApiKey(models.Model):
customer = models.ForeignKey(UserProfile)
key = models.CharField(max_length=40, default=new_key)
And admin.py is
class ApiKeyInline(admin.StackedInline):
model = ApiKey
extra = 0
class UserProfileAdmin(admin.ModelAdmin):
inlines = [ApiKeyInline]
admin.site.register(UserProfile, UserProfileAdmin)
When using admin page api key get correctly populated with a random value. However when saving the UserProfile it doesn't get saved, it is as if nothing was added. If I manually change a a single leter in the autogenerated key saving works correctly. It seems to me this is a problem with django detecting a change or something like this.
Any suggestions? Code worked in 1.4.
There is a solution in one of the old threads: How to force-save an "empty"/unchanged django admin inline?
You should mark your inline form as always changed.
from django.forms import ModelForm
from .models import UserProfile
class AlwaysChangedModelForm(ModelForm):
def has_changed(self):
""" Should returns True if data differs from initial.
By always returning true even unchanged inlines will get validated and saved."""
return True
#An inline model
class ApiKey(admin.StackedInline):
model = ApiKey
extra = 0
form = AlwaysChangedModelForm
A short investigation lead me to the save_new_objects of class BaseModelFormSet, located in django/forms/models.py.
It has the following check: if not form.has_changed()
Looks promising, huh? Now, we want to "enhance" this method. Where to start? Well... Inlines inherit from InlineModelAdmin, which has an get_formset method. So...
class ApiKeyInline(admin.StackedInline):
model = ApiKey
extra = 0
def get_formset(self, *args, **kwargs):
formset = super(EmailInlineAdmin, self).get_formset(*args, **kwargs)
# at this point, formset is a generated class called like "ApiKeyFormSet"
# as it is a regular python objects, no one stops us from playing with it
formset.save_new_objects = ApiKeyInline.my_own_save_new_objects_method
return formset
def my_own_save_new_objects_method(self, commit=True):
# here should be something like
# django/forms/models.py BaseModelFormSet.save_new_objects
return self.new_objects
The contents of my_own_save_new_objects_method you have to edit yourself. The original method, once again, is in dist-packages/django/forms/models.py , either call it via super or write something entirely yours, anyways skip the check.
Also, maybe it is a terrible and overcomplicated solution. I have a feeling that there should be a better one. In example, not setting default=new_key in your model, but setting a default value for the form field, in example.

Can I disable a field in the Rest Framework API browsing view

I am using Django Rest Framework to serialize a model in which I have a foreignkey.
models.py
class Article(models.Model):
author = models.ForeignKey(Author, related_name='articles')
... other fields...
serializers.py
class ArticleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Article
I want to get rid of the 'HTML form' at the bottom of the browsable API view since I get a list with all my articles and retrieving them from the DB takes ages (I have some 100K articles, and each time the html form is displayed, my server does 100K queries).
I have read the answer from How to disable admin-style browsable interface of django-rest-framework? and I am currently displaying the view in JSON. However, I like the html view and would like to find a way to avoid the html form available at the bottom.
I don't want to properly remove the field from the view (I need to use it), but just remove the database queries used to populate the form.
Any idea ?
Making the field read-only also means you cannot modify it, which is probably not wanted in all cases.
Another solution is to override the BrowsableApiRenderer so it won't display the HTML form (which can be indeed really slow with a lot of data).
This is surprisingly easy, just override get_rendered_html_form:
from rest_framework.renderers import BrowsableAPIRenderer
class NoHTMLFormBrowsableAPIRenderer(BrowsableAPIRenderer):
def get_rendered_html_form(self, *args, **kwargs):
"""
We don't want the HTML forms to be rendered because it can be
really slow with large datasets
"""
return ""
then adjust your settings to use this renderer:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'myapp.renderers.NoHTMLFormBrowsableAPIRenderer',
)
}
I answer my own question.
I found in the documentation the solution to my problem. I had to use the read_only attribute.
serializers.py
class ArticleSerializer(serializers.HyperlinkedModelSerializer):
author = serializers.RelatedField(read_only=True)
class Meta:
model = Article
fields = ('author', ...other_fields)
#maerteijn answer will disable all forms: POST, PUT, DELETE and OPTIONS.
If you still want to allow the awesome "OPTIONS" button, you can do something like this
class NoHTMLFormBrowsableAPIRenderer(BrowsableAPIRenderer):
OPTIONS_METHOD = "OPTIONS"
def get_rendered_html_form(self, data, view, method, request):
if method == self.OPTIONS_METHOD:
return super().get_rendered_html_form(data, view, method, request)
else:
"""
We don't want the HTML forms to be rendered because it can be
really slow with large datasets
"""
return ""
And modify settings.py in the same way
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'myapp.renderers.NoHTMLFormBrowsableAPIRenderer',
)
}

Pre-selecting multiple checkboxes in Django forms

Very similar to this question, but I tried the accepted answer and it did not work. Here's what's going on.
I have a form for tagging people in photos that looks like this:
forms.py
class TaggingForm(forms.Form):
def __init__(self, *args, **kwargs):
queryset = kwargs.pop('queryset')
super(TaggingForm, self).__init__(*args, **kwargs)
self.fields['people'] = forms.ModelMultipleChoiceField(required=False, queryset=queryset, widget=forms.CheckboxSelectMultiple)
...
models.py
class Photo(models.Model):
user = models.ForeignKey(User)
...
class Person(models.Model):
user = models.ForeignKey(User)
photos = models.ManyToManyField(Photo)
...
I want users to be able to edit the tags on their photos after they initially tag them, so I have a page where they can go to view a single photo and edit its tags. For obvious reasons I want to have the already-tagged individuals' checkboxes pre-selected. I tried to do this by giving the form's initial dictionary a list of people I wanted selected, as in the answer to the question I linked above.
views.py
def photo_detail(request,photo_id):
photo = Photo.objects.get(id=photo_id)
initial = {'photo_id':photo.id, 'people':[p for p in photo.person_set.all()]}
form_queryset = Person.objects.filter(user=request.user)
if request.method == "POST":
form = TaggingForm(request.POST, queryset=form_queryset)
# do stuff
else:
form = TaggingForm(initial=initial, queryset=form_queryset)
...
When I try to initialize people as in the above code, the form doesn't show up, but no errors are thrown either. If I take the 'people' key/value pair out of the initial dictionary the form shows up fine, but without any people checked.
Also I'm using Django 1.5 if that matters. Thanks in advance.
What you could do is simply use django forms to handle all of this for you. Please refer to this question. Ideally it boils down to lettings djnago handle your forms and its validation and initial values.
Now this is actually a really good practice to get used to since, you're dissecting all your logic and your presentation. Its a great DRY principle.