Django: Change the parent, when the child get deleted(unidirectional OneToOne) - python

I want to change Photo 'labeled' field False when I delete LabeledPhoto object.
#models.py
class Photo(models.Model):
image = models.ImageField(upload_to='shoes_data/%Y/%m/%d', name='image')
created = models.DateTimeField(auto_now_add=True)
labeled = models.BooleanField(default=False)
class LabeledPhoto(models.Model):
labeled_image = models.OneToOneField(Photo, on_delete=models.PROTECT, related_name='labeled_image')
topcategory = models.CharField(max_length=64)
subcategory = models.CharField(max_length=64)
labeler = models.CharField(max_length=32)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
I tried like this but, It didn't work
# views.py
class LabeledPhotoDelete(DeleteView):
model = LabeledPhoto
template_name = 'label/labeled_photo_delete.html'
success_url = reverse_lazy('photo:labeled_list')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
labeled = LabeledPhoto.objects.get(id=self.object.pk)
labeled.labeled_image.labeled = False
labeled.save()
self.object.delete()
return reverse(success_url)

You need to .save() the Photo:
from django.http import HttpResponseRedirect
class LabeledPhotoDelete(DeleteView):
model = LabeledPhoto
template_name = 'label/labeled_photo_delete.html'
success_url = reverse_lazy('photo:labeled_list')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
image = self.object.labeled_image
image.labeled = False
image.save()
self.object.delete()
return HttpResponseRedirect(success_url)
But there is no reason to store whether the image is labeled in a field: you can simply look that up if you want to know this. Indeed, you can add a property:
class Photo(models.Model):
image = models.ImageField(upload_to='shoes_data/%Y/%m/%d', name='image')
created = models.DateTimeField(auto_now_add=True)
#property
def labeled(self):
try:
return self.labeled_image is not None
except LabeledPhoto.DoesNotExist:
return False
or annotate the queryset with the fact if a LabeledPhoto exists:
from django.db.models import BooleanField, ExpressionWrapper, Q
Photo.objects.annotate(
is_labeled=ExpressionWrapper(
Q(labeled_image__isnull=False),
output_field=BooleanField()
)
)

Related

Django Class based UpdateView with Form for Multiple Uploaded Files

I have two questions in regards to the problem I am currently facing:
Is it best practice in django to overwrite the post method in the CreateView? If it isn't do you write a form _valid function on the CategoryFullForm or in the CreateView and how would it look? The CreateView currently works great, but want to make sure there isn't a better way to do this.
If this is best practice, how would you override the get function in the UpdateView so you would be able to edit the files that relate to the instance being uploaded and even add an additional File? Open to other ways to accomplish this task as well.
models.py
############ CATEGORY MODEL ################
class Category(models.Model):
category_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, null=True, blank=True)
est_pr_sqft = models.FloatField(blank=True)
est_duration = models.IntegerField(blank=True)
preceding_job = models.CharField(max_length=100, blank=True)
category_notes = models.CharField(max_length=250, blank=True)
category_slug = models.SlugField(max_length=100, unique=True, null=False)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
date_posted = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('category-detail', kwargs={'slug': self.category_slug})
def save(self, *args, **kwargs):
if not self.category_slug:
self.category_slug = slugify(self.name)
return super().save(*args, **kwargs)
class CategoryFiles(models.Model):
category_id = models.ForeignKey(Category, on_delete=models.CASCADE)
document = models.FileField(upload_to="attachments",null=True,blank=True)
uploaded_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
def delete(self, *args, **kwargs):
self.document.delete()
return super().delete(*args, **kwargs)
forms.py
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ['name', 'est_pr_sqft', 'est_duration', 'preceding_job', 'category_notes']
class CategoryFullForm(CategoryForm):
files = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}),required=False)
class Meta(CategoryForm.Meta):
fields = CategoryForm.Meta.fields + ['files']
views.py
####### CATEGORY VIEWS ########
class CategoryListView(LoginRequiredMixin, ListView):
model = Category
template_name = 'ProjectManagementApp/PM-Category-List.html'
context_object_name = 'categories'
slug_field = 'project_slug'
success_url = reverse_lazy('Category-list')
class CategoryDetailView(LoginRequiredMixin, DetailView):
model = Category
template_name = 'ProjectManagementApp/PM-Category-Detail.html'
slug_field = 'category_slug'
def get_context_data(self, **kwargs):
context = super(CategoryDetailView, self).get_context_data(**kwargs)
context['files'] = CategoryFiles.objects.all()
#context['photos'] = CategoryPhotos.objects.all()
return context
class CategoryCreateView(LoginRequiredMixin, CreateView):
model = Category
form_class = CategoryFullForm
template_name = 'ProjectManagementApp/PM-Category-Create.html' # Replace with your template.
#success_url = reverse_lazy('category-detail')
slug_field = 'category_slug'
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('files')
if form.is_valid():
author = request.user
name = form.cleaned_data['name']
est_pr_sqft = form.cleaned_data['est_pr_sqft']
est_duration = form.cleaned_data['est_duration']
preceding_job = form.cleaned_data['preceding_job']
category_notes = form.cleaned_data['category_notes']
category_obj = Category.objects.create(name=name,est_pr_sqft=est_pr_sqft,est_duration=est_duration,preceding_job=preceding_job,category_notes=category_notes,author=author)
for f in files:
CategoryFiles.objects.create(category_id=category_obj,document=f)
return HttpResponseRedirect(reverse_lazy('Category-list'))
else:
return self.form_invalid(form)
class CategoryUpdateView(LoginRequiredMixin, UpdateView): #UserPassesTestMixin - makes sure user who made entry is only one who can update it.
model = Category
form_class = CategoryFullForm
template_name = 'ProjectManagementApp/PM-Category-Create.html' # Replace with your template.
slug_field = 'category_slug'
success_url = reverse_lazy('Category-list')
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('files')
if form.is_valid():
author = request.user
name = form.cleaned_data['name']
est_pr_sqft = form.cleaned_data['est_pr_sqft']
est_duration = form.cleaned_data['est_duration']
preceding_job = form.cleaned_data['preceding_job']
category_notes = form.cleaned_data['category_notes']
category_obj = Category.objects.create(name=name,est_pr_sqft=est_pr_sqft,est_duration=est_duration,preceding_job=preceding_job,category_notes=category_notes,author=author)
for f in files:
CategoryFiles.objects.create(category_id=category_obj,document=f)
return HttpResponseRedirect(reverse_lazy('Category-list'))
else:
return self.form_invalid(form)
#
# #Doesnt allow you to update other users post
# #def test_func(self):
# # project = self.get_object()
# # if self.request.user == post.author:
# # return True
# # return False
#
#
def form_valid(self,form):
form.instance.author = self.request.user
return super().form_valid(form)
class CategoryDeleteView(LoginRequiredMixin, DeleteView): #UserPassesTestMixin - makes sure user who made entry is only one who can update it.
model = Category
template_name = 'ProjectManagementApp/PM-Category-Delete.html'
slug_field = 'category_slug'
success_url = reverse_lazy('Category-list')
#Doesnt allow you to delete other users post
#def test_func(self):
# project = self.get_object()
# if self.request.user == post.author:
# return True
# return False
If the urls.py/admin.py are needed am happy to provide, but don't think they are needed, as well as any of the html files.

Django: Expected view likeList to be called with a URL keyword argument named "id"

Ive been trying to create an API that would return all objects from Like model however, I received an error (Expected view likeList to be called with a URL keyword argument named "id". Fix your URL conf, or set the .lookup_field attribute on the view correctly.).
Here is my model
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
objects = models.Manager()
image = models.ImageField(upload_to='post_pics')
def __str__(self):
return self.title
#property
def useremail(self):
return self.author.email
#property
def owner(self):
return self.author
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk':self.pk})
def get_api_url(self, request=None):
return api_reverse('post-detail', kwargs={'pk': self.pk}, request=request)
def get_like_count(self):
return self.like_set.count()
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
Serializer
class likeserializers(serializers.ModelSerializer):
username = serializers.SerializerMethodField(read_only=True)
post_title = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Like
fields = ('id','created',
'user','username',
'post','post_title')
def get_username(self, obj):
return obj.user.username
def get_post_title(self, obj):
return obj.post.title
Views
class likeList(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'id'
serializer_class = likeserializers
def get_queryset(self):
return Like.objects.all()
URLS
urlpatterns = [
path('users/', API_views.userList.as_view(), name = 'users'),
path('users/id=<int:id>/', API_views.userListbyID.as_view(), name = 'usersid'),
path('posts/', API_views.postList.as_view(), name = 'post'),
path('posts/id=<int:id>', API_views.postListbyID.as_view(), name = 'postid'),
path('likes/', API_views.likeList.as_view(), name = 'likes'),
path('likes/id=<int:id>', API_views.likeListbyID.as_view(), name = 'likesid'),
path('follows/', API_views.followList.as_view(), name = 'likes'),
path('follows/id=<int:id>', API_views.followListbyID.as_view(), name = 'likesid'),
]
As per the error I should either fix my URL conf, or set the .lookup_field attribute on the view correctly. It works as intended if I change my URL conf however, if I use only lookup_field it fix the issue. I have completely the same view for posts and it works.
Post serializer:
class postserializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
like_count = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Post
fields = ('url','id',
'title','content',
'date_posted','author',
'useremail','like_count')
def get_url(self,obj):
request = self.context.get("request")
return obj.get_api_url(request=request)
def get_like_count(self,obj):
return obj.get_like_count()
def validate_title(self,value):
qs = Post.objects.filter(title__iexact = value)
#exclude the same instance
if self.instance:
qs = qs.exclude(pk=self.instance.pk)
#if title already exists raise error
if qs.exists():
raise serializers.ValidationError(f"Post with title '{value}' already exists")
return value
and post view:
class postList(mixins.CreateModelMixin, generics.ListAPIView):
lookup_field = 'id'
serializer_class = postserializer
permission_classes = [IsOwnerOrReadOnly]
# permissions can be set up here as well + in settings.py
# permission_classes =
def get_queryset(self):
qs = Post.objects.all()
query = self.request.GET.get("q")
if query is not None:
qs = qs.filter(
Q(title__icontains = query)|
Q(content__icontains = query)
).distinct()
return qs
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
What am I missing? What is the difference between my post and like views/serializers which are causing this issue?

Hit Count/Page View Functionality Django App

The question has been asked, and most suggest using django-hitcount. https://pypi.org/project/django-hitcount/
I tried that solution. But that solution is for python 2 use. Not python 3 use. Trying that solution I ran into the error described in this post:
Django Hit Count ImportError
So I am trying to create this functionality. I have an auction item model. I am persisting viewCount among other fields:
models.py
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
from django.utils import timezone
class AuctionItem(models.Model):
seller = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='auction_items')
title = models.CharField(max_length=100)
description = models.TextField()
startBid = models.FloatField()
buyNowPrice = models.FloatField()
buyNowEnabled = models.BooleanField()
deliveryCost = models.FloatField()
startDate = models.DateTimeField(default=timezone.now)
endDate = models.DateTimeField(default=timezone.now)
viewCount=models.IntegerField(default=0)
def __str__(self):
return self.title
def save(self):
super().save()
img = Image.open(self.image.path)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.image.path)
def getCurrentTopBid():
return startBid
def incrementViewCount(self):
self.viewCount += 1
self.save()
I have a class based view class AuctionItemDetailView(DetailView) for the auction item detail view, and in it I am trying to increment the viewCount in the model by calling incrementViewCount()
views.py
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.mixins import (
LoginRequiredMixin,
UserPassesTestMixin
)
from django.contrib.auth.models import User
from django.views.generic import (
ListView,
DetailView,
CreateView,
UpdateView,
DeleteView,
)
from .models import AuctionItem
class AuctionItemListView(ListView):
model = AuctionItem #looks dir of app name #template #view
template_name = 'auctionitem/auctionitem_list.html' #<app>/<model>_<viewtype>.html
context_object_name = 'auctionitems'
ordering = ['-startDate']
paginate_by = 2
class UserAuctionItemListView(ListView):
model = AuctionItem #looks dir of app name #template #view
template_name = 'auctionitem/user_auctionitems.html' #<app>/<model>_<viewtype>.html
context_object_name = 'auctionitems'
ordering = ['-date_posted']
paginate_by = 2
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return AuctionItem.objects.filter(seller=user).order_by('-startDate')
class AuctionItemDetailView(DetailView):
model = AuctionItem
model.incrementViewCount()
class AuctionItemCreateView(LoginRequiredMixin, CreateView):
model = AuctionItem
fields = ['image', 'title', 'description', 'startBid', 'buyNowPrice', 'buyNowEnabled', 'startDate', 'endDate', 'deliveryCost', 'seller']
success_url ='/'
def form_valid(self,form):
form.instance.seller = self.request.user
return super().form_valid(form)
class AuctionItemUpdateView(LoginRequiredMixin,UpdateView):
model = AuctionItem
fields = ['title', 'description', 'startBid', 'buyNowPrice', 'buyNowEnabled', 'startDate', 'deliveryCost', 'seller']
def form_valid(self,form):
form.instance.seller = self.request.user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == auctionitem.seller:
return True
return False
class AuctionItemDeleteView(LoginRequiredMixin,UserPassesTestMixin, DeleteView):
model = AuctionItem
success_url ='/'
def test_func(self):
post = self.get_object()
if self.request.user == auctionitem.seller:
return True
return False
But when I run the server I get the following error:
TypeError: incrementViewCount() missing 1 required positional argument: 'self'
I tried passing 'self', and got the following error:
NameError: name 'self' is not defined
How can I make this work?
Thanks,
Ironman
In your AuctionItemDetailView
def get(self, request, *args, **kwargs):
res = super().get(request, *args, **kwargs)
self.object.incrementViewCount()
return res

Django Form saving

I would like to share with you my code in order to find a solution. I have a Django form. I would like to save data only if there is not another object with same data. In other words, objects should be unique.
If the object doesn't exist, I save it, else I display the form with an error message 'The object already exists with this features'.
This is my model:
def guide_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/guides/<guide_id>
new_filename = f'guides/{instance.site.id}'
if instance.profile_type:
new_filename += f'_{instance.profile_type}'
if instance.profile_level:
new_filename += f'_{instance.profile_level}'
if instance.language:
new_filename += f'_{instance.language}'
name, ext = os.path.splitext(filename)
if ext:
new_filename += ext
return new_filename
class UserGuide(models.Model):
""" A class for storing user guides depending on profiles """
objects = models.Manager()
site = models.ForeignKey(WebApplication, on_delete=models.PROTECT, related_name='guides',
verbose_name=_('application'))
file = models.FileField(verbose_name='file', upload_to=guide_path)
profile_type = models.CharField(verbose_name=_('profile type'), choices=UserProfile.USER_TYPES, max_length=2,
null=True, blank=True)
profile_level = models.CharField(verbose_name=_('profile level'), choices=UserProfile.USER_ROLES, max_length=2,
null=True, blank=True)
language = models.CharField(verbose_name=_('language'), choices=settings.LANGUAGES, max_length=2, default='en')
class Meta:
verbose_name = _('user guide')
verbose_name_plural = _('user guides')
unique_together = ('site', 'profile_type', 'profile_level', 'language')
This is my form:
class UserGuideForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class Meta:
model = UserGuide
fields = ['site', 'file', 'profile_type', 'profile_level', 'language']
widgets = {
'file': CustomFileInput(attrs={'class': 'clearablefileinput'}),
}
And this is my view:
class UserGuideUpdateView(UpdateView):
""" Display a form to create a userguide
**Context**
``subtitle``
Title of the page
**Template:**
:template:`app/generic_form.html`
"""
model = UserGuide
form_class = UserGuideForm
success_url = reverse_lazy('userguide-list')
template_name = 'app/form_userguide.html'
permission_required = 'app.change_userguide'
def get_object(self, queryset=None):
pk = self.kwargs.get('pk')
return get_object_or_404(UserGuide, pk=pk)
def get_title(self):
return _('Edit user guide: ')
def get_context_data(self, **kwargs):
context = super(UserGuideUpdateView, self).get_context_data(**kwargs)
context.update({
'subtitle': self.get_title(),
})
return context
def form_valid(self, form):
site = form.cleaned_data['site']
file = form.cleaned_data['file']
profile_type = form.cleaned_data['profile_type']
profile_level = form.cleaned_data['profile_level']
language = form.cleaned_data['language']
userguide = UserGuide.objects.filter(site=site.id, profile_type=profile_type, profile_level=profile_level, language=language)
if userguide.exists():
messages.error(self.request, _('A user guide for that profile and language already exists'))
HttpResponseRedirect(self.template_name)
else:
pass
return super().form_valid(form)
How I can add condition, if my object already exists, not save the form and return the form with the error message ?
Thank you

Django: Use post() in UpdateView to populate form's fields with instance's existing field values

I have trouble getting the current instance's fields on my UpdateView. How do I get the specific instance based on its id?
views.py
class ShowUpdate(UpdateView):
model = Show
fields = ['description', 'season', 'episode']
def post(self, request, **kwargs):
request.POST = request.POST.copy()
request.POST['description'] = "how to get instance description?" # problem here
request.POST['season'] = 2
return super(ShowUpdate, self).post(request, **kwargs)
models.py
class Show(models.Model):
owner = models.ForeignKey(User, null=True, default=True, related_name='o')
title = models.CharField(max_length=100)
description = models.TextField(default='N/A', blank=True, max_length=250)
season = models.IntegerField(default=0)
episode = models.IntegerField(default=0)
def get_absolute_url(self):
return reverse('show:index')
def __str__(self):
return self.title
Look to the UpdateView docs
This View has method get_object(self, queryset=None)
In you case just need to call it in POST method something like this:
class ShowUpdate(UpdateView):
model = Show
fields = ['description', 'season', 'episode']
def post(self, request, **kwargs):
self.object = self.get_object()
request.POST = request.POST.copy()
request.POST['description'] = self.object.description
request.POST['season'] = 2
return super(ShowUpdate, self).post(request, **kwargs)

Categories

Resources