Django rest framework ModelViewSet displaying custom HTML instead of default - python

I am new to Django & DRF and I am trying to replace the default api view given from ModelViewSet (scr1,scr2,scr3) with custom HTML templates. Default view works just fine, i've tested with postman and it can do CRUD functions(at least it seems so), but I have no idea how to substitute the default views with custom html pages. I did follow DRF docs - it worked, also searcged solutions(this one was promising), but I simply can't adopt it my situation. Please help!
models.py:
from django.db import models
class Package(models.Model):
prod_name = models.CharField(max_length=255, default=0)
quantity = models.IntegerField(default=0)
unit_price = models.IntegerField(default=0)
def __str__(self):
return self.prod_name
class Orders(models.Model):
order_id = models.CharField(max_length=255, default=0)
package = models.ManyToManyField(Package)
is_cod = models.BooleanField(default=False)
def __str__(self):
return self.order_id
serializers.py:
from rest_framework import serializers
from .models import Package, Orders
class PackageSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField()
prod_name = serializers.CharField(max_length=255, default=0)
quantity = serializers.IntegerField(default=0)
unit_price = serializers.IntegerField(default=0)
class Meta:
model = Package
fields = "__all__"
class OrderSerializer(serializers.HyperlinkedModelSerializer):
package = PackageSerializer(many=True)
def get_or_create_packages(self, packages):
print("this is package in gocp:", packages)
package_ids = []
i = 0
for package in packages:
print("i=", i)
i += 1
package_instance, created = Package.objects.get_or_create(pk=package.get('id'), defaults=package)
print("package id:", package.get('id'))
package_ids.append(package_instance.pk)
print("package_ids:", package_ids)
return package_ids
def create_or_update_packages(self, packages):
package_ids = []
for package in packages:
package_instance, created = Package.objects.update_or_create(pk=package.get('id'), defaults=package)
package_ids.append(package_instance.pk)
return package_ids
def create(self, validated_data):
print("this is validated_data:", validated_data)
package = validated_data.pop('package', [])
print("this is package:", package)
order = Orders.objects.create(**validated_data)
order.package.set(self.get_or_create_packages(package))
return order
def update(self, instance, validated_data):
package = validated_data.pop('package', [])
instance.package.set(self.create_or_update_packages(package))
fields = ['order_id', 'is_cod']
for field in fields:
try:
setattr(instance, field, validated_data[field])
except KeyError: # validated_data may not contain all fields during HTTP PATCH
pass
instance.save()
return instance
class Meta:
model = Orders
fields = "__all__"
views.py:
from .serializers import OrderSerializer, PackageSerializer
from .models import Package, Orders
from rest_framework import viewsets
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
queryset = Orders.objects.all()
class PackageViewSet(viewsets.ModelViewSet):
serializer_class = PackageSerializer
queryset = Package.objects.all()
urls.py:
from django.urls import path, include
router = DefaultRouter()
router.register(r'order', OrderViewSet, basename='orders')
router.register(r'package', PackageViewSet, basename='package')
urlpatterns = [
path('api/', include(router.urls)),
]
I am assuming that:
list(), create()/update() needs to be modified in views.py (class OrderViewSet & PackageViewSet), but how? I did try many different options to define - list(), create()/update() functions, but nothing worked, for example(in this particular case im not allowed to overide existing list method):
#action (renderer_classes=[TemplateHTMLRenderer], detail=True)
def list(self, request, *args, **kwargs):
template_name = 'order_list.html'
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, template_name='order_list.html')
Actual html templates need to conform to the nested model that I have and I am not sure how it would work with nested data. Since I can't solve the 1st point, can't test out and try to solve the 2nd point.
Will rendering_form serializer work? Taking the DRF docx exaple from work?:
{% load rest_framework %}
<html><body>
<h1>Orders - {{ orders.order_id }}</h1>
<form action="{% url 'order-detail' pk=orders.pk %}" method="POST">
{% csrf_token %}
{% render_form serializer %}
<input type="submit" value="Save">
</form>
</body></html>
Perhaps I should forget ModelsViewSet and go with APIView?(though in this particular case if there exist a solution I would prefer to know it).

Related

Forbidden (CSRF token missing.): /ckeditorupload/ in django?

When i try to upload an image in the admin panel of django, i get this Forbidden (CSRF token missing.): /ckeditorupload/ i have this result in my console.
here my model
from django.db import models
from django.contrib.auth.models import User
from ckeditor.fields import RichTextField
from ckeditor_uploader.fields import RichTextUploadingField
# Create your models here.
STATUS = (
(0,"Draft"),
(1,"Publish")
)
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete= models.CASCADE,related_name='blog_posts')
updated_on = models.DateTimeField(auto_now= True)
#content = RichTextField(blank=True, null=True)
image = RichTextUploadingField()
#image = models.ImageField(upload_to='featured_image/%Y/%m/%d/') #
created_on = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(choices=STATUS, default=0)
class Meta:
ordering = ['-created_on']
def __str__(self):
return self.title
my views
from django.views import generic
from .models import Post
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
#method_decorator(csrf_exempt, name='dispatch')
class PostList(generic.ListView):
queryset = Post.objects.filter(status=1).order_by('-created_on')
template_name = 'index.html'
class PostDetail(generic.DetailView):
model = Post
template_name = 'post_detail.html'
I tried to exempt the CSRF but i get same issues, & when i delete the middlewareCsrf it's dont works too, since i'm using the admin django and not a custom post method, i dont know where to pass the {{ csrf_token }}
Thanks for your help :)
I had a similar problem some time ago, it was as if all of a sudden there was no csrf_token available anymore in the user session. After a bit of tinkering this solved it.
import:
from django.middleware.csrf import get_token
in the view:
csrf_token = get_token(request)
context = {
"csrf_token": csrf_token,
}
In the template I still use {% csrf_token %}, but I also make some API calls in JavaScript and in those ones I instead used directly the token that i passed through the context -> {{ csrf_token }}

ModelMultipleChoiceField django and debug mode

i'm trying to implement a ModelMultipleChoiceField in my application, like that: Link
model.py
class Services(models.Model):
id = models.AutoField(primary_key=True)
type = models.CharField(max_length=300)
class Professionals_Services(models.Model):
professional = models.ForeignKey(User, on_delete=models.CASCADE)
service = models.ForeignKey(Services, on_delete=models.CASCADE)
form.py
class ProfileServicesUpdateForm(forms.ModelForm):
service = forms.ModelMultipleChoiceField(required=False, queryset=Services.objects.all())
class Meta:
model = Professionals_Services
fields = ['service']
def clean(self):
# this condition only if the POST data is cleaned, right?
cleaned_data = super(ProfileServicesUpdateForm, self).clean()
print(cleaned_data.get('service'))
view.py
class EditProfileServicesView(CreateView):
model = Professionals_Services
form_class = ProfileServicesUpdateForm
context_object_name = 'services'
template_name = 'accounts/edit-profile.html'
#method_decorator(login_required(login_url=reverse_lazy('professionals:login')))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(self.request, *args, **kwargs)
def post(self, request, *args, **kwargs):
form = self.form_class(data=request.POST)
if form.is_valid():
services = form.save(commit=False)
services.save()
html
<select class="ui search fluid dropdown" multiple="" name="service" id="id_service">
{% for service in services_list %}
<option value="{{ service.id }}">{{ service.type }}</option>
{% endfor %}
</select>
For development i'm using Pycham Professionals(latest version) with docker, when i run the application and i try to make a POST the answer is:
Cannot assign "<QuerySet [<Services: Services object (2)>, <Services: Services object (5)>, <Services: Services object (6)>, <Services: Services object (7)>]>": "Professionals_Services.service" must be a "Services" instance.
But if i run the application in debug mode and with a breakpoints on the if form.is_valid():
the application works fine
That's because the validate is equal to Unknown not in debug
you know how to fix?
Your service is a ForeignKey:
service = models.ForeignKey(Services, on_delete=models.CASCADE)
A ForeignKey means that you select a single element, not multiple ones. You use a ManyToManyField [Django-doc] to select multiple elements:
class Professionals_Services(models.Model):
professional = models.ForeignKey(User, on_delete=models.CASCADE)
service = models.ManyToManyField(Service)
You should also not override the post method, and you can make use of the LoginRequiredMixin [Django-doc] to ensure that the user is logged in:
from django.contrib.auth.mixins import LoginRequiredMixin
class EditProfileServicesView(LoginRequiredMixin, CreateView):
login_url = reverse_lazy('professionals:login')
model = Professionals_Services
form_class = ProfileServicesUpdateForm
context_object_name = 'services'
template_name = 'accounts/edit-profile.html'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
In your Form you should also return the cleaned data:
class ProfileServicesUpdateForm(forms.ModelForm):
service = forms.ModelMultipleChoiceField(required=False, queryset=Services.objects.all())
class Meta:
model = Professionals_Services
fields = ['service']
def clean(self):
# this condition only if the POST data is cleaned, right?
cleaned_data = super(ProfileServicesUpdateForm, self).clean()
print(cleaned_data.get('service'))
return cleaned_data
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: Models in Django are written in PerlCase, not snake_case,
so you might want to rename the model from Professionals_Services to ProfessionalService.
Note: normally a Django model is given a singular name, so Services instead of Service.

(Django) Send POST ID back to the Client

I'm new to Django and I'm trying to create a somewhat basic API. But one thing that has been bugging me is how to create a callback function for when certain (asynchronous) events occur.
For example, one of my simplest cases is for when the client sends a POST request. I would like to send back to him the ID of that request in the DB. How would I do that?
My code mostly follows William S. Vincent's Django for APIs book with minor modifications.
The most important parts are:
models.py:
from django.db import models
class SegmentcliApi(models.Model):
request_id = models.AutoField(primary_key = True)
database = models.CharField(max_length = 100)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __str__(self):
return f'DB Request to {self.database}: created at {self.created_at}'
serializers.py:
from rest_framework import serializers
from .models import SegmentcliApi
class SegmentcliApiSerializer(serializers.ModelSerializer):
class Meta:
fields = (
'request_id',
'database',
'created_at',
'updated_at',
)
model = SegmentcliApi
views.py:
from rest_framework import generics
from .models import SegmentcliApi
from .serializers import SegmentcliApiSerializer
class SegmentcliApiList(generics.ListCreateAPIView):
queryset = SegmentcliApi.objects.all()
serializer_class = SegmentcliApiSerializer
class SegmentcliApiDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = SegmentcliApi.objects.all()
serializer_class = SegmentcliApiSerializer
In your views.py, more specifically, in the CreateApiView or ListCreateApiView classes, you can override the create method such that you can get a specific response from post request.
The create method comes from the CreateModelMixin class, which is inherited by the ListCreateAPIView, and which is in turn inherited by your specific Django model.
So follow the steps below:
from rest_framework import status
from rest_framework.response import Response
class SegmentcliApiList(generics.ListCreateAPIView):
queryset = SegmentcliApi.objects.all()
serializer_class = SegmentcliApiSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
# Original Response (inside the `CreateModelMixin` class)
# return Response(
# serializer.data,
# status=status.HTTP_201_CREATED,
# headers=headers
# )
# We will replace the original response with this line
return Response(
{'id': serializer.data.get('request_id')},
status=status.HTTP_201_CREATED,
headers=headers
)
# serailizer.data is the created object in a JSON format
This works for me. If it is not working for you, tell me what you're getting in comments. If it works for you, please accept the answer.
I would suggest starting to look at Django Forms and specifically Model Form, which is what I think SegmentcliApiSerializer is supposed to be. Then, I'm not sure how your urls.py is set up to work with your app, but in your template, the form would be something like:
<form action="{% url 'name_of_your_url_to_get_to_view_method' %}" method="POST">
{% csrf_token %}
{{ form }}
<button type="submit">BUTTON</button>
</form>
and view.py would be something like this:
def yourViewMethod(request):
if(request.method == "POST"):
form = SegmentcliApiSerializer(request.POST)
if(form.is_valid()):
form.save()
savedId = request.POST.get("request_id")
return render(request, "yourFolder/yourHtmlFile.html", {"savedId": savedId})
else:
form = SegmentcliApiSerializer()
return render(request, "yourFolder/yourHtmlFile.html", {"form": form})
and then, in whatever yourHtmlFile.html is, you can use
{{ savedId }}
somewhere to display it. I think that's what you're asking.
EDIT: Fixed so it got the value correctly.
I can't comment cause I don't have enough reputation but, with respect to #marsonfire's answer, if you want to have only simple JSON response you can use JsonResponse instead of render.
So, you'd have something like return JsonResponse({"savedId": savedId})

Fetching a random item from the view/object list using Django

UPDATE #2
Status: Still not solved
Updated: Thurs. Dec. 18, 11:30 a.m.
I'm currently using FullArticle.objects.order_by('?').first() to get a random article from my database, but it's not working. There is probably something missing from my models, view or url.py that's missing.
models.py
from django.db import models
from django.core.urlresolvers import reverse
# Create your models here.
class FullArticleQuerySet(models.QuerySet):
def published(self):
return self.filter(publish=True)
class FullArticle(models.Model):
title = models.CharField(max_length=150)
author = models.CharField(max_length=150)
slug = models.SlugField(max_length=200, unique=True)
pubDate = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
category = models.CharField(max_length=150)
heroImage = models.CharField(max_length=250, blank=True)
relatedImage = models.CharField(max_length=250, blank=True)
body = models.TextField()
publish = models.BooleanField(default=True)
gameRank = models.CharField(max_length=150, blank=True, null=True)
objects = FullArticleQuerySet.as_manager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("FullArticle_detailed", kwargs={"slug": self.slug})
class Meta:
verbose_name = "Blog entry"
verbose_name_plural = "Blog Entries"
ordering = ["-pubDate"]
views.py
from django.views import generic
from . import models
from .models import FullArticle
# Create your views here.
class BlogIndex(generic.ListView):
queryset = models.FullArticle.objects.published()
template_name = "list.html"
randomArticle = FullArticle.objects.order_by('?').first()
class BlogDetail(generic.DetailView):
model = models.FullArticle
template_name = "detailed.html"
urls.py
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns(
'',
url(r'^$', views.BlogIndex.as_view(), name="list"),
url(r'^(?P<slug>\S+)', views.BlogDetail.as_view(), name="detailed"),
)
Section in list.html that I want to be random
<div class="mainContent clearfix">
<div class="wrapper">
<h1>Top 10 Video Games</h1>
{% for article in object_list|slice:":1" %}
<p class="date">{{article.pubDate|date:"l, F j, Y" }}</p> | <p class="author">{{article.author}}</p>
<img src="{{article.heroImage}}" alt="" class="mediumImage">
<p class="caption">{{article.body|truncatewords:"80"}}</p>
{% endfor %}
I assume that FullArticle.objects.order_by('?')[0] will give me a
random item from my class of FullArticle. But, let's say that out of
my model, I only want data associated with the specific parts of the
article: title, author, heroImage and body. How would I go about doing
that?
To get specific fields of an object, use values or values_list. The first will return dictionaries, the second tuples:
FullArticle.objects.order_by('?').values('title','author','heroImage','body').first()
The above would result in something like:
{'title': 'Foo', 'author': 'Foo Author', ... }
I've also tacked on your suggestion of random =
FullArticle.objects.order_by('?')[0] called it "random" instead.
Not sure what this is about, but try to avoid shadowing built-in libraries, like random.
1) Actually you almost did it.
try:
article = FullArticle.objects.order_by('?')[0]
except IndexError:
article = None
2) You could use this in models.py as well as in views.py. IMHO there is no need to extract this string to separate method so I would write this code wherever I need it.
3) Better use ORM don't convert db result to list to choose first item. This is can be really memory and CPU expensive.
Getting a random article would usually be done in a view, or as a modelmanager method, or as a class method. Fullarticle.random should not be a class attribute. That will not work as you expect.
# Used in a view.
article = FullArticle.objects.order_by('?').first()
# you can also make a random() method in your model manager.
def random(self):
return self.get_queryset().order_by('?').first()
# or as a class method inside FullArticle
#classmethod
def random(cls):
return cls.objects.order_by('?').first()
I'm not quite sure what exactly you mean by this.
I only want data associated with the specific parts of the article: title, author, heroImage and body. How would I go about doing that?
To access specific attributes you do this:
title = article.title
author = article.author
If you don't need to use article.category, just don't access it.
from django.views.generic import DetailView
from books.models import Publisher, Book
To pass data from your (class based) View to the template it has to be added to the context.
Here's an example from the official documentation:
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
source: https://docs.djangoproject.com/en/1.7/topics/class-based-views/generic-display/#adding-extra-context
Lots of people find Class Based Views in Django to be a bit confusing. I would recommend that you understand how function based views work before you start doing anything complicated with CBVs.

Django: Extending User Model - Inline User fields in UserProfile

Is there a way to display User fields under a form that adds/edits a UserProfile model? I am extending default Django User model like this:
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
about = models.TextField(blank=True)
I know that it is possible to make a:
class UserProfileInlineAdmin(admin.TabularInline):
and then inline this in User ModelAdmin but I want to achieve the opposite effect, something like inverse inlining, displaying the fields of the model pointed by the OneToOne Relationship (User) in the page of the model defining the relationship (UserProfile). I don't care if it would be in the admin or in a custom view/template. I just need to know how to achieve this.
I've been struggling with ModelForms and Formsets, I know the answer is somewhere there, but my little experience in Django doesn't allow me to come up with the solution yet. A little example would be really helpful!
This has been brought up before.
Here's a blog post with what I think is my favorite solution. The gist is to use two ModelForms, and render them into a single <form> tag in the template making use of the prefix kwarg:
http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/
Here's another method which I like a bit less, but is also valid. They use two separate <form>s on the page, with different actions and two submit buttons:
Proper way to handle multiple forms on one page in Django
This one talks more specifically about Users and UserProfiles:
How to create a UserProfile form in Django with first_name, last_name modifications?
Update
Here is what I ended up with
# models.py
class UserProfile(models.Model):
favorite_color = models.CharField(max_length=30)
user = models.OneToOneField(User)
# forms.py
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
# we fill the 'user' value in UserCreateView.form_valid
exclude = ('user',)
# views.py
from django.contrib.auth.forms import UserCreationForm
class UserCreateView(FormView):
# url to redirect to after successful form submission
success_url = reverse_lazy('user_list')
template_name = "userform.html"
def get_context_data(self, *args, **kwargs):
data = super(UserCreateView, self).get_context_data(*args, **kwargs)
data['userform'] = self.get_form(UserCreationForm, 'user')
data['userprofileform'] = self.get_form(UserProfileForm, 'userprofile')
return data
def post(self, request, *args, **kwargs):
forms = dict((
('userform', self.get_form(UserCreationForm, 'user')),
('userprofileform', self.get_form(UserProfileForm, 'userprofile')),
))
if all([f.is_valid() for f in forms.values()]):
return self.form_valid(forms)
else:
return self.form_invalid(forms)
def get_form(self, form_class, prefix):
return form_class(**self.get_form_kwargs(prefix))
def get_form_kwargs(self, prefix):
kwargs = super(UserCreateView, self).get_form_kwargs()
kwargs.update({'prefix': prefix})
return kwargs
def form_valid(self, forms):
user = forms['userform'].save()
userprofile = forms['userprofileform'].save(commit=False)
userprofile.user_id = user.id
userprofile.save()
return HttpResponseRedirect(self.get_success_url())
def get(self, request, *args, **kwargs):
return self.render_to_response(self.get_context_data())
# userform.html
<form action="" method="POST" class="form">
{% csrf_token %}
{{ userform.as_p }}
{{ userprofileform.as_p }}
<button type="submit">Submit</button>
</form>
# urls.py
...
url(r'^create/$', UserCreateView.as_view(), name='user_create'),
...

Categories