Writing my first "real" django project I realized that my views are really huge. I try to convert it into CBV (as additional django training). Here are my fbv views:
def index(request):
context = {}
categories = Category.objects.select_related()
subcategories = SubCategory.objects.all()
context['categories'] = categories
context['subcategories'] = subcategories
return render(request, 'mainapp/index.html', context)
def category(request, category_name_slug):
context = {}
try:
category = Category.objects.get(slug=category_name_slug)
subcategory = SubCategory.objects.filter(category=category)
context['subcategories'] = subcategory
context['category'] = category
except Category.DoesNotExist:
raise Http404("Category doesn't exist")
return render(request, 'mainapp/category.html', context)
def subcategory(request, category_name_slug, subcategory_name_slug):
context = {}
form = SiteAddForm(initial={'url': 'http://'})
context['form'] = form
try:
category = Category.objects.get(slug=category_name_slug)
subcategory = SubCategory.objects.filter(category=category
).get(slug=subcategory_name_slug)
sites = Site.objects.filter(subcategory=subcategory, is_active=True)
context['subcategory'] = subcategory
context['category'] = category
context['sites'] = sites
except (SubCategory.DoesNotExist, Category.DoesNotExist):
raise Http404("Nie ma takiej strony")
if request.method == 'POST':
form = SiteAddForm(request.POST)
if form.is_valid():
siteurl = form.cleaned_data['url']
context['siteurl'] = siteurl
try:
page = AddNewSite(siteurl)
except:
print('ERRROR')
context['Error'] = "URL is not responding"
return render(request, 'mainapp/subcategory.html', context)
title = page.title()
description = page.description()
keywords = page.keywords()
if page.check_url_in_database():
context['Error'] = "Site already in database"
return render(request, 'mainapp/subcategory.html', context)
else:
form_extended = SiteAddFormFull(initial={'url': siteurl, 'name':
title, 'description':
description, 'keywords':
keywords})
context['form_extended'] = form_extended
if request.method == 'POST':
form_extended = SiteAddFormFull(request.POST)
if form_extended.is_valid():
keywords = form_extended.cleaned_data['keywords']
name = form_extended.cleaned_data['name']
description = form_extended.cleaned_data['description']
page.add_site(category, subcategory, keywords,
description, name, siteurl)
context['added_site'] = 'Thank you - site was added'
messages.add_message(request, messages.SUCCESS, 'Dodano!!')
return render(request, 'mainapp/subcategory.html', context)
How Can I simpy write my base view (index) using CBV. It should look that way:
Category:
Sub1
Sub2
Category2:
Sub2a
Sub2b
Should I learn CBV or stay using FBV (it works)? Below are my Category, Subcategory models:
class Category(models.Model):
name = models.CharField(max_length=30, unique=True,
verbose_name='Category name')
slug = models.SlugField()
image = models.ImageField(upload_to='category_images',
verbose_name="Category image",
blank=True)
description = models.TextField(default='Description',
verbose_name="Category description")
class Meta:
verbose_name_plural = "Categories"
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
def image_thumb(self):
if self.image:
return '<img src="/media/%s" width="40" height="40" />' % (self.image)
else:
return('')
image_thumb.short_description = 'Thumb'
image_thumb.allow_tags = True
def __str__(self):
return self.name
class SubCategory(models.Model):
category = models.ForeignKey(
'Category',
related_name='subcategory',
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=30)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(SubCategory, self).save(*args, **kwargs)
class Meta:
verbose_name_plural = "Subcategories"
def __str__(self):
return self.name
I'm an advocate of CBV, it can make life a lot easier when trying to reuse code. There are also a ton of pre-defined generic views that only need a small tweaks for most instances. For your example index, you can use a TemplateView:
class IndexView(TemplateView):
template_name = "mainapp/index.html"
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['categories'] = Category.objects.select_related()
context['subcategories'] = SubCategory.objects.all()
return context
Related
Im trying to handle the existing name of a Category, so that users wont be allowed to create 2 categories with the same name, but at the moment its taking all categories from the database, not only for the logged-in user. I dont know how and where to implement request.user.
I`m building an inventory app where everyone creates their own categories and adds items.
Thank you.
This is my model:
class Category(models.Model):
user = models.ForeignKey(User, default=1, on_delete=models.CASCADE,
related_name='category', null=True)
name = models.CharField(max_length=100, null=False, blank=False)
slug = models.SlugField(max_length=100)
created_on = models.DateTimeField(auto_now_add=True)
timestamp = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-timestamp']
verbose_name = 'category'
verbose_name_plural = 'categories'
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('category_detail', kwargs={'slug': self.slug})
This is my form:
class CategoryForm(forms.ModelForm):
add_category = forms.BooleanField(widget=forms.HiddenInput, initial=True)
class Meta:
model = Category
fields = ['name']
def clean_name(self):
name = self.cleaned_data.get('name')
if (name == ""):
raise forms.ValidationError('This field cannot be left blank')
for instance in Category.objects.all():
if instance.name == name:
raise forms.ValidationError('There is a category with the name ' + name)
return name
This is my view:
#login_required
def index(request):
categories = Category.objects.filter(user=request.user)
items = Item.objects.all()
add_item = ItemForm()
add_category = CategoryForm()
query = None
if request.method == 'POST':
if 'add_item' in request.POST:
add_item = ItemForm(request.POST)
if add_item.is_valid():
form = add_item.save(commit=False)
form.category = get_object_or_404(
Category, name=request.POST.get('category'),
user=request.user)
add_item.save()
return redirect('home')
else:
add_category = CategoryForm(request.POST)
if add_category.is_valid():
category_form = add_category.save(commit=False)
category_form.save()
messages.success(request, f'{name} has been added')
return redirect('home')
Edit Category View
#login_required
def category_edit(request, pk):
category = Category.objects.get(id=pk)
if request.method == 'POST':
form = CategoryForm(request.POST, instance=category)
if form.is_valid():
form.save()
messages.info(request, f'{category.name} has been updated!')
return redirect('home')
else:
form = CategoryForm(instance=category, user=request.user)
context = {
'form': form,
}
return render(request, 'category_edit.html', context)
Ive tried adding user = request.user in the form class, but that resulted in an error Ive tried adding category_form.user = request.user before saving the form but that was still taking names from every other user
Pass the request's user to the form:
class CategoryForm(forms.ModelForm):
add_category = forms.BooleanField(widget=forms.HiddenInput, initial=True)
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
class Meta:
model = Category
fields = ['name']
def clean_name(self):
name = self.cleaned_data.get('name')
if (name == ""):
raise forms.ValidationError('This field cannot be left blank')
qs = Category.objects.filter(user=self.user, name=name)
if self.instance.pk:
# EXCLUDE CURRENT INSTANCE TO ENABLE EDIT
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
raise forms.ValidationError('There is a category with the name ' + name)
return name
then in the view you need to pass the user:
#login_required
def index(request):
categories = Category.objects.filter(user=request.user)
items = Item.objects.all()
add_item = ItemForm()
add_category = CategoryForm(user=request.user)
query = None
if request.method == 'POST':
if 'add_item' in request.POST:
add_item = ItemForm(request.POST)
if add_item.is_valid():
form = add_item.save(commit=False)
form.category = get_object_or_404(
Category, name=request.POST.get('category'),
user=request.user)
add_item.save()
return redirect('home')
else:
add_category = CategoryForm(user=request.user, data=request.POST)
if add_category.is_valid():
category_form = add_category.save(commit=False)
category_form.save()
messages.success(request, f'{name} has been added')
return redirect('home')
class Post(models.Model):
cat_post = models.ForeignKey(Category, on_delete=models.CASCADE, blank=True,null=True)
top_post = models.ForeignKey(TopicsCategory, on_delete=models.CASCADE, blank=True,null=True)
sub_post = models.ForeignKey(SubTopicsCategory, on_delete=models.CASCADE, blank=True,null=True)
class CreatePostView(CreateView):
model = Post
template_name = 'blog/create.html'
form_class = CreatePostForm
def get_context_data(self, *args, **kwards):
print(self.kwargs)
context = super(CreatePostView, self).get_context_data(**kwards)
context['btn'] = 'Add'
return context
def form_valid(self, form, *args, **kwargs):
if self.kwargs.get('category_slug') and len(self.kwargs) == 1:
category = Category.objects.get(slug=self.kwargs['category_slug'])
form.instance.cat_post = category
return super(CreatePostView, self).form_valid(form)
# передача в форму kwargs view
def get_form_kwargs(self):
kwargs = super(CreatePostView, self).get_form_kwargs()
kwargs.update({'view_kwargs': self.kwargs})
return kwargs
def get_success_url(self):
return reverse('topics_category_list', kwargs={'category_slug': self.kwargs['category_slug'], })
class CreatePostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['name', 'text', 'discussion']
# widgets = {
# 'cat_post': forms.HiddenInput(),
# }
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("view_kwargs")
super(CreatePostForm, self).__init__(*args, **kwargs)
def clean_name(self):
name = self.cleaned_data['name']
if self.request.get('category_slug') and len(self.request) == 1:
category = Category.objects.get(slug=self.request['category_slug'])
unique = Post.objects.filter(slug=slugify(self.cleaned_data['name']), cat_post=category.pk,discussion=False).exists()
if unique:
raise ValidationError(f'Post is not unique')
return name
**
I have duplicate sources in db here form_valid and clean_name.
How do I pass the form in the view form form_valid class instance that I got from the database to clean_name.
There will be posts for 2 models and requests will increase
My project is a discussion forum using Django and here are my create and update functions. The method of update_post should provide update functionality but every time I try to update a post it adds a new post. How can I update a resource?
#login_required
def create_post(request):
context = {}
form = PostForm(request.POST or None)
if request.method == "POST":
if form.is_valid():
print("\n\n its valid")
author = Author.objects.get(user=request.user)
new_post = form.save(commit=False)
new_post.user = author
new_post.save()
form.save_m2m()
return redirect("home")
context.update({
"form": form,
"title": "Create New Post"
})
return render(request, "create_post.html", context)
#login_required
def update_post(request):
context = {}
author = Author.objects.get(user=request.user)
form = PostForm(request.POST , instance=author)
if request.method == "POST":
if form.is_valid():
print("\n\n its valid")
new_post = form.save(commit=False)
# new_post.user = author
new_post.save()
form.save_m2m()
return redirect("home")
context.update({
"form": form,
"title": "UpdatePost",
})
return render(request, "update_post.html", context)
In model total there are 4 classes Post , comment , reply and category and this is Post -
class Post(models.Model):
title = models.CharField(max_length=400)
slug = models.SlugField(max_length=400, unique=True, blank=True)
user = models.ForeignKey(Author, on_delete=models.CASCADE)
content = HTMLField()
categories = models.ManyToManyField(Category)
date = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=True)
hit_count_generic = GenericRelation(HitCount, object_id_field='object_pk',
related_query_name='hit_count_generic_relation'
)
tags = TaggableManager()
comments = models.ManyToManyField(Comment, blank=True)
closed = models.BooleanField(default=False)
state = models.CharField(max_length=40, default="zero")
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
def __str__(self):
return self.title
def get_url(self):
return reverse("detail", kwargs={
"slug":self.slug
})
#property
def num_comments(self):
return self.comments.count()
#property
def last_reply(self):
return self.comments.latest("date")
form = PostForm(request.POST , instance=author)
instance feels like it should be passed an instance of Post instead of an instance of Author. I assume that PostForm is a pretty standard ModelForm with model = Post in the Meta.
I am trying to make a nested comment functionality using django rest framework.
I am using Django 2.2 and rest framework version 3.10.2 but here i am following a video in youtube that uses Django 1.9.
No matter what value i passed in URL, it always return a validation error stating that
model_qs is empty.
I am not able to solve this issue. Can someone please look into it and
let me point out what i am doing wrong here.
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
urls.py
urlpatterns = [
path('create/', CommentCreateAPIView.as_view(), name='create'),
]
model.py
class CommentManager(models.Manager):
def all(self):
qs = super(CommentManager, self).filter(parent=None)
return qs
def filter_by_instance(self, instance):
content_type = ContentType.objects.get_for_model(instance.__class__)
obj_id = instance.id
qs = super(CommentManager, self).filter(content_type=content_type, object_id=obj_id).filter(parent=None)
return qs
def create_by_model_type(self, model_type, slug, content, user, parent_obj=None):
model_qs = ContentType.objects.filter(model=model_type)
if model_qs.exists():
some_model = model_qs.first().model_class()
obj_qs = some_model.objects.filter(slug=slug)
if obj_qs.exists() and obj_qs.count() == 1:
instance = self.model()
instance.content = content
instance.user = user
instance.content_type = model_qs.first()
instance.object_id = obj_qs.first().id
if parent_obj:
instance.parent = parent_obj
instance.save()
return instance
return None
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE)
content = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
objects = CommentManager()
class Meta:
ordering = ['-timestamp']
def __unicode__(self):
return str(self.user.username)
def __str__(self):
return str(self.user.username)
def get_absolute_url(self):
return reverse("comments:thread", kwargs={"id": self.id})
def get_delete_url(self):
return reverse("comments:delete", kwargs={"id": self.id})
def children(self): # Replies
return Comment.objects.filter(parent=self)
#property
def is_parent(self):
if self.parent is not None:
return False
return True
serializers.py
def create_comment_serializer(model_type='post', slug=None, parent_id=None, user=None):
class CommentCreateSerializer(ModelSerializer):
class Meta:
model = Comment
fields = [
'id',
'parent',
'content',
'timestamp',
]
def __init__(self, *args, **kwargs):
self.model_type = model_type
self.slug = slug
self.parent_obj = None
if parent_id:
parent_qs = Comment.objects.filter(id=parent_id)
if parent_qs.exists() and parent_qs.count() == 1:
self.parent_obj = parent_qs.first()
return super(CommentCreateSerializer, self).__init__(*args, **kwargs)
def validate(self, data):
model_type = self.model_type
model_qs = ContentType.objects.filter(model=model_type)
print(model_qs) # This always return <QuerySet []>
if not model_qs.exists() or model_qs.count() != 1:
raise ValidationError("This is not a valid content types")
some_model = model_qs.first().model_class()
obj_qs = some_model.objects.filter(slug=self.slug)
if not obj_qs.exists() or obj_qs.count() != 1:
raise ValidationError("This is not a valid slug")
return data
def create(self, validated_data):
content = validated_data.get("content")
if user:
main_user = user
else:
main_user = User.objects.all().first()
model_type = self.model_type
slug = self.slug
parent_obj = self.parent_obj
comment = Comment.objects.create_by_model_type(model_type, slug, content, main_user, parent_obj=parent_obj,)
return comment
return CommentCreateSerializer
views.py
class CommentCreateAPIView(CreateAPIView):
queryset = Comment.objects.all()
# permission_classes = [IsAuthenticated]
def get_serializer_class(self):
model_type = self.request.GET.get("type")
slug = self.request.GET.get("slug")
parent_id = self.request.GET.get("parent_id", None)
return create_comment_serializer(
model_type=model_type,
slug=slug,
parent_id=parent_id,
user=self.request.user
)
Am I going about this the right way? Having never used 'generic views' I am tying to use Django's generic.UpdateView view. When I 'hit' the 'update' button on the form, I get an invalid form response with message 'Library with this Slide name already exists'
Grateful for any help.
View:
class Slideview(generic.UpdateView):
model = Library
template_name = 'app1/slide_update.html'
fields = ['slide_name', 'reference_value','esd',
'current_mean', 'counts_averaged', 'status']
context_object_name = 'qc_slide'
#def get_queryset(self):
#slide_id = self.kwargs['pk']
#return Library.objects.filter(slide_name=slide_id)
def get_success_url(self):
return reverse('Slideview', args=[self.kwargs['pk']])
def get_context_data(self, **kwargs):
context = super(Slideview, self).get_context_data(**kwargs)
#form = self.get_form(self.get_form_class())
#context['form'] = form
return context
def post(self, request, *args, **kwargs):
print("Im in post")
form = self.get_form(self.get_form_class())
if form.is_valid():
#Code will go here which will query a second model
#perform a series of math calculations and then
#return the updated information
self.object = self.get_object()
self.object.save()
return self.form_valid(form)
else:
print("Form not valid")
self.object = self.get_object()
return self.form_invalid(form)
Model:
class Library(models.Model):
slide_name = models.CharField(max_length=5, primary_key=True)
reference_value = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
esd = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
current_mean = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
counts_averaged = models.IntegerField(default=0)
status = models.CharField(max_length=9)
def __str__(self):
return self.slide_name