If I'll send get request like thisenter image description here, i need to have multiple pagination (LimitOffset and PageNumber).
models.py:
from django.db import models
class Products(models.Model):
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
photo = models.ImageField(upload_to="photos/%Y/%m/%d/", null=True)
hashtag = models.CharField(max_length=255)
is_hit = models.BooleanField(default=False)
category = models.ForeignKey('Category', on_delete=models.PROTECT, null=True)
def __str__(self):
return self.title
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
views.py:
from rest_framework import generics
from rest_framework.pagination import PageNumberPagination
from .models import *
from .serializers import ProductsSerializer
class PaginationProducts(PageNumberPagination):
page_size = 2
page_size_query_param = 'page_size'
max_page_size = 2
class ProductsAPIList(generics.ListCreateAPIView):
queryset = Products.objects.all()
serializer_class = ProductsSerializer
pagination_class = PaginationProducts
serializers.py
from rest_framework import serializers
from .models import *
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = "__all__"
def get_photo_url(self, obj):
request = self.context.get('request')
photo_url = obj.fingerprint.url
return request.build_absolute_uri(photo_url)
I need something that can help API client choose number of page and quantity of posts on that page. Think that in this case i need NumberPagePagination and LimitOffsetPagination.
I think you don't need to create the custom pagination class.
from rest_framework.pagination import LimitOffsetPagination
class ProductsAPIList(generics.ListCreateAPIView):
queryset = Products.objects.all()
serializer_class = ProductsSerializer
pagination_class = LimitOffsetPagination
The offset value corresponds to the page * size in the original pagination and the limit value corresponds to the size.
I'm new to Python and DjangoRestFramework. I am trying to create an image upload system with image-tagging. "Tags" have a many-to-many relationship with "Images". The forms are in the React.js in the front-end. I am trying to understand how to write a view for this. I have not seen a clear solution to this online.
here is upload/models.py
from django.db import models
from django.db.models.fields import UUIDField
from django.contrib.postgres.functions import RandomUUID
def upload_path(instance, filename):
return '/'.join(['images', str(instance.contributor), str(instance.caption), str(instance.date_created), filename])
class Image(models.Model):
image = models.ImageField(upload_to=upload_path)
contributor = models.ForeignKey(
'accounts.User', related_name='+', on_delete=models.CASCADE, default='user0')
caption = models.CharField(max_length=100)
date_created = models.DateTimeField(auto_now_add=True, null=True)
id = UUIDField(primary_key=True, default=RandomUUID, editable=False)
theme = models.CharField(max_length=10)
class Tags(models.Model):
tag = models.ManyToManyField(Image, through='Junction')
class Junction(models.Model):
image = models.ForeignKey(Image, on_delete=models.CASCADE)
tags = models.ForeignKey(Tags, on_delete=models.CASCADE)
upload/serializers.py
from rest_framework import serializers
from .models import Image, Theme, Tags, Junction
class TagsSerializer(serializers.ModelSerializer):
tags = serializers.PrimaryRelatedKeyField(
queryset=Image.object.all(), many=True)
class Meta:
model = Tags
fields = ('Tags')
class ImageSerializer(serializers.ModelSerializer):
tags_list = TagsSerializer(many=True, read_only=True)
class Meta:
model = Image
fields = ('image', 'contributor', 'caption', 'date_created', 'id')
class JunctionSerializer(serializers.ModelSerializer):
class Meta:
model = Junction
fields = ('image', 'theme', 'tags')
Here are two possible solutions for the upload/views.py:
from django.shortcuts import render
from django.http import HttpResponse
from rest_framework import viewsets
from .serializers import JunctionSerializer
from .models import Image, Tags, Junction
#SOLUTION_1:
class JunctionView(viewsets.ModelViewSet):
serializer_class = JunctionSerializer
query_set = Junction.objects.all()
def get_context_data(self, request, *args, **kwargs):
image = request.data['cover']
tags = request.data['tags']
Junction.objects.create(image=image, tags=tags)
return HttpResponse({'message': 'Successful Upload'}, status=200)
#SOLUTION_2
class JunctionView():
????
def get_context_data(self, **kwargs):
context = super(JunctionView, self).get_context_data(**kwargs)
context['image'] = Image.objects.all()
context['tags'] = Tags.objects.all()
return context
Is it necessary to call the context? The second solution that I researched is not specific to Django REST Framework, I think..
One approach is to create a ModelViewSet for the images, since you are working mainly with managing the images.
class ImageViewSet(viewsets.ModelViewSet):
serializer_class = ImageSerializer
query_set = Image.objects.all()
And then using your serializer, just modify your serializer to handle the m2m relations with a through model as described here. So something like:
class ImageSerializer(serializers.ModelSerializer):
tags_list = TagsSerializer(many=True, read_only=True)
class Meta:
model = Image
fields = ('image', 'contributor', 'caption', 'date_created', 'id')
def create(self, validated_data):
tags_list = validated_data.pop('tags_list')
instance = super().create(validated_data)
for tag in tags_list:
Junction.objects.create(image=instance, tag=tag)
This is not tested so it might have some problems here and there but this is the gist.
I have created some nice models in models.py for uploading multiple images in single products, for different products. The good thing it uses one image model for all products. Now i'm failing to create a perfect forms.py. May someone help me please.
from django.db import models
from django.utils.safestring import mark_safe
from ckeditor_uploader.fields import RichTextUploadingField
from mptt.fields import TreeForeignKey
from mptt.models import MPTTModel
from django.urls import reverse
from django import forms
from django.forms import ModelForm, Form, TextInput, Textarea
def get_upload_path(instance, filename):
model = instance.album.model.__class__._meta
name = model.verbose_name_plural.replace(' ', '_')
return f'{name}/images/{filename}'
class ImageAlbum(models.Model):
def default(self):
return self.images.filter(default=True).first()
def thumbnails(self):
return self.images.filter(width__lt=100, length_lt=100)
class Image(models.Model):
name = models.CharField(max_length=255)
image = models.ImageField(upload_to=get_upload_path)
default = models.BooleanField(default=False)
width = models.FloatField(default=100)
length = models.FloatField(default=100)
album = models.ForeignKey(ImageAlbum, related_name='images', on_delete=models.CASCADE)
class Product(models.Model):
title = models.CharField(max_length=20)
album = models.OneToOneField(ImageAlbum, related_name='model', on_delete=models.CASCADE)
class Vehicle(Product):
STATUS = (
('True', 'True'),
('False', 'False'),
)
brand = models.CharField(max_length=30)
price = models.DecimalField(max_digits=8, decimal_places=2)
mileage = models.IntegerField()
...
I'm trying to pass two models into a create view, where i am trying to get the the primary key from the URL to retrieve the details from a food truck model so it can be displayed in the page, and where a user can write a review about food truck. Also, I'd like a list of the reviews to be displayed on the page.
views.py
class TruckReviewView(CreateView):
model = Review
template_name = 'truckReviews/detail.html'
fields = ['speedOfService', 'qualityAndTaste', 'valueForMoney', 'comment']
def get_queryset(self):
self.pk = self.kwargs['pk']
queryset = super(TruckReviewView, self).get_queryset()
return queryset
def get_context_data(self, **kwargs):
context = super(TruckReviewView, self).get_context_data(**kwargs)
context['truck'] = FoodTrucks.objects.get(truckID=get_queryset())
context['reviews'] = Review.objects.get(truckID=get_queryset())
return context
urls.py
urlpatterns = [
path('', TruckListView.as_view(), name='reviews-home'),
path('truck/<int:pk>/', TruckReviewView.as_view(), name='truck-detail'),
path('about/', About.as_view(), name='reviews-about'),
]
models.py
class FoodTrucks(models.Model):
truckID = models.IntegerField(primary_key=True, unique=True, null=False)
name = models.CharField(max_length=25)
category = models.CharField(max_length=20)
bio = models.TextField()
avatarSRC = models.TextField(default=None)
avatarALT = models.CharField(max_length=20, default=None)
avatarTitle = models.CharField(max_length=20, default=None)
coverPhotoSRC = models.TextField(default=None)
coverPhotoALT = models.CharField(max_length=20, default=None)
coverPhotoTitle = models.CharField(max_length=20, default=None)
website = models.TextField(default=None)
facebook = models.CharField(max_length=100, default=None)
instagram = models.CharField(max_length=30, default=None)
twitter = models.CharField(max_length=15, default=None)
class Review(models.Model):
reviewID = models.AutoField(primary_key=True, unique=True, serialize=False, null=False)
truckID = models.ForeignKey(FoodTrucks, on_delete=models.CASCADE)
userID = models.ForeignKey(User, on_delete=models.CASCADE)
datePosted = models.DateTimeField(default=timezone.now)
speedOfService = models.IntegerField()
qualityAndTaste = models.IntegerField()
valueForMoney = models.IntegerField()
comment = models.TextField(max_length=128)
I've tried to use get_queryset to get the pk from the URL and pass the pk into get_context_data and target the specific truck with that ID in the database.
The difficulty comes from the fact that you combine a list view and create view. If you want to combine this into one view, then you need to do a bit mixing and matching with different mixins of the Class Based Views.
It can be done, but it's not trivial. If you're new to Django, then this may be overshooting things. I've renamed fields an such and did it as an exercise. I haven't bothered with the form submission, it shouldn't be that difficult to implement as the other views don't deal with the methods involved (form_valid, get_success_url, etc). You can use it as a guide to see what you should be learning. The above linked site is extremely convenient to see how things are mixed together.
The result below will provide the variables "foodtruck", "reviews" and "form" to the template.
import typing as t
from django.views import generic
from .models import FoodTruck, Review
from .forms import ReviewForm
if t.TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse
from django.contrib.auth.models import AbstractUser
class AuthenticatedRequest(HttpRequest):
user: AbstractUser = ...
class FoodTruckDetailReviewListCreateView(
generic.list.MultipleObjectMixin, generic.edit.CreateView,
):
template_name = "foodtrucks/detail.html"
model = Review
list_model = Review
context_list_name = "reviews"
context_object_name = "foodtruck"
detail_model = FoodTruck
form_class = ReviewForm
def get(self, request: "AuthenticatedRequest", *args, **kwargs) -> "HttpResponse":
"""
Combine the work of BaseListView and BaseDetailView
Combines the get implementation of BaseListView and BaseDetailView, but
without the response rendering. Then hands over control to CreateView's
method to do the final rendering.
Some functionality is stripped, because we don't need it.
:param request: The incoming request
:return: A response, which can be a redirect
"""
# BaseListView
self.object_list = self.get_queryset()
# BaseDetailView
self.object = self.get_object()
context = self.get_context_data(
object=self.object, object_list=self.object_list
)
# CreateView sets self.object to None, but we override form_kwargs, so
# we can leave it at a value.
return self.render_to_response(context=context)
def get_template_names(self):
# Bypass logic in superclasses that we don't need
return [self.template_name]
def get_object(self, queryset=None):
# We provide the queryset to superclasses with the other model
return super().get_object(queryset=self.detail_model.objects.all())
def get_queryset(self):
# This only gets called by MultipleObjectMixin
pk = self.kwargs.get(self.pk_url_kwarg)
if pk is None:
raise AttributeError(
"Unable to filter on food truck: {} is missing in url.".format(
self.pk_url_kwarg
)
)
queryset = self.list_model.objects.filter(food_truck_id=pk)
# print(str(queryset.query))
return queryset
def get_context_data(self, **kwargs):
if "object" in kwargs:
kwargs[self.context_object_name] = kwargs["object"]
if "object_list" in kwargs:
kwargs[self.context_list_name] = kwargs["object_list"]
return super().get_context_data(**kwargs)
def get_form_kwargs(self):
# Bypass ModelFormMixin, which passes in self.object as instance if it
# is set.
return super(generic.edit.ModelFormMixin, self).get_form_kwargs()
And as a reference, this is what I changed the models to:
import uuid
from django.contrib.auth import get_user_model
from django.db import models
from django.utils import timezone
class FoodTruck(models.Model):
name = models.CharField(max_length=25)
category = models.CharField(max_length=20)
bio = models.TextField()
avatar_url = models.URLField(blank=True)
avatar_alt_text = models.CharField(max_length=20, blank=True)
avatar_title = models.CharField(max_length=20, blank=True)
cover_photo_url = models.URLField(blank=True)
cover_photo_alt_text = models.CharField(max_length=20, default="No photo provided")
cover_photo_title = models.CharField(max_length=20, default="No photo provided")
website = models.URLField(blank=True)
facebook = models.CharField(max_length=100, blank=True)
instagram = models.CharField(max_length=30, blank=True)
# https://9to5mac.com/2017/11/10/twitter-display-name-limit/
twitter = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.name
class Review(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
food_truck = models.ForeignKey(
FoodTruck, on_delete=models.CASCADE, related_name="reviews"
)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
posted_at = models.DateTimeField(default=timezone.now)
speed_of_service = models.IntegerField()
quality_and_taste = models.IntegerField()
value_for_money = models.IntegerField()
comment = models.TextField(max_length=128)
def __str__(self):
return "Review about {} by {}".format(
self.food_truck.name, self.user.get_full_name()
)
And finally the form (with a bit of trickery to inject bootstrap classes):
class ReviewForm(forms.ModelForm):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for field in self.fields.values():
if not field.widget.is_hidden:
field.widget.attrs.setdefault("class", "form-control")
class Meta:
model = Review
exclude = ("uuid", "user", "food_truck", "posted_at")
First, there's not need to create a truckID and reviewID primary key fields because Django creates a unique id field for each object automatically on which you can simply do .get(id=1) or .filter(id=1) etc.
Just like it is completely useless to put ID in fields with Foreign Key or any relational fields because Django will automatically take the name and append _id to it. For instance, just user would become user_id or truck would be truck_id in backend on which you can do .get(user__id=1) or .get(user_id=1) for example.
You should review this section of your code. You're actually not doing anything with the primary key:
def get_queryset(self):
queryset = super().get_queryset()
try:
item = queryset.get(id=self.kwargs['pk'])
except:
...
else:
# Do something with item here
...
finally:
return queryset
or, with get_context_data:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
queryset = super().get_queryset()
try:
item = queryset.get(id=kwargs['pk'])
except:
...
else:
# Do something with item here
context['item'] = item
finally:
return context
I'm sorry but your question is a bit confusing. If you're trying to get details from a model you should use a DetailView. Also, on a DetailView assuming you want the details of a Review since you have the truck on a review you could simply override the get_context_data and set truck in the context by doing self.object.truck.
If you're trying to create a review then it's right to use the CreateView but that should only be for the Review model.
To list you should use a ListView.
So, to my understanding, you have a truckID and want to create a review for that. In that case, it'd have a CreateView for Review model.
Have a look at the CreateView, DetailView and ListView docs
I am trying to create a ModelForm for my Model Class "Asset" in Django 3
from django.db import models
from django.contrib.auth.models import User
from django.forms import ModelForm
class Manufacturer(models.Model):
name = models.CharField(max_length=100)
class Asset(models.Model):
serial = models.CharField(max_length=200)
manufacturer = models.ManyToManyField(Manufacturer)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=200)
I managed to create a Form via the following code
from django import forms
from .models import Manufacturer
class AssetForm(forms.Form):
serial = forms.CharField(max_length=150)
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all().values('name'))
name = forms.CharField(max_length=200)
description = forms.TextInput()
status = forms.CharField(max_length=200)
category = forms.CharField(max_length=200)
The querySet results in a dropdown being filled out with either "{'name':'Apple'}" or "('Apple',)" depending on using values or values_list respectively. How can I just display the name itself?
Adding the following method to the model fixes the problem:
def __str__(self):
return self.name
This will return the name and only the name to the queryset.