How to delete / edit posts as staff or superuser? - django

I am fairly new to Django and right now not very familiar with the concept of Django. I created a user board (from simpleisbetterthancomplex.com) and would like to implement a Moderation with a "is_staff" user.
For now, only the user which created a post / comment is able to edit the post. As a is_staff user, I also want be able to edit ALL posts / comments.
This is the edit button:
{{ post.message }}
{% if post.created_by == user %}
<div class="mt-3">
<a href="{% url 'edit_post' post.topic.board.pk post.topic.pk post.pk %}"
class="btn btn-primary btn-sm"
role="button">Edit</a>
</div>
{% endif %}
I thought I could so something like:
{{ post.message }}
{% if user.is_staff %}
<div class="mt-3">
<a href="{% url 'edit_post' post.topic.board.pk post.topic.pk post.pk %}"
class="btn btn-primary btn-sm"
role="button">Edit</a>
</div>
{% endif %}
Although I reach the correct hyperlink to edit the post, I receive a "Page not found" error.
What could be an approach to implement some Moderation to my forum?

It's hard to tell what's wrong with your code. It seems both code samples should display the same button.
Please note that in this situation you should likely use a single button and tag, by changing the if to a slightly more complicated {% if user.is_staff or post.created_by == user %}. This should have the added effect of eliminating all possible discrepancies between the two buttons.
If you just want the ability to edit/delete posts, then the simplest way would likely be to use the built-in django admin panel. If you used django startproject, then your app already has one! Try going to localhost:8000/admin (by default) to check it out.
https://docs.djangoproject.com/pl/2.1/ref/contrib/admin/
EDIT: I think I can see the problem. You filter your queryset in PostUpdateView by (created_by=self.request.user). This filter works differently when dealing with a different user, such as the moderator. Try changing the view to reflect this.

The issue is the URL returned by edit_post. This view only allows access to owners of the post, so no one else can access this view.
You would need to add to the view to allow users with is_staff = True to also access this view.
The problem is with the queryset definition filtering out models not created by the user. But you want to keep this, so that other users don't have access.
You could remove the get_queryset call and adjust the dispatch method to only allow the owner or staff members to view. But I suggest keeping this intact and add a new moderator update view and use django braces to sort out the permissions. Something like;
from braces.views import StaffuserRequiredMixin
class ModeratorUpdateView(LoginRequiredMixin,
StaffuserRequiredMixin,
UpdateView):
## Update Code here ##

The view of my board:
class BoardListView(ListView):
model = Board
context_object_name = 'boards'
template_name = 'home.html'
class TopicListView(ListView):
model = Topic
context_object_name = 'topics'
template_name = 'topics.html'
paginate_by = 5
def get_context_data(self, **kwargs):
kwargs['board'] = self.board
return super().get_context_data(**kwargs)
def get_queryset(self):
self.board = get_object_or_404(Board, pk=self.kwargs.get('pk'))
queryset = self.board.topics.order_by('-last_updated').annotate(replies=Count('posts') - 1)
return queryset
#login_required
def new_topic(request, pk):
board = get_object_or_404(Board, pk=pk)
if request.method == 'POST':
form = NewTopicForm(request.POST)
if form.is_valid():
topic = form.save(commit=False)
topic.board = board
topic.starter = request.user # <- here
topic.save()
Post.objects.create(
message=form.cleaned_data.get('message'),
topic=topic,
created_by=request.user # <- and here
)
return redirect('topic_posts', pk=pk, topic_pk=topic.pk) # TODO: redirect to the created topic page
else:
form = NewTopicForm()
return render(request, 'new_topic.html', {'board': board, 'form': form})
def topic_posts(request, pk, topic_pk):
topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk)
topic.views += 1
topic.save()
return render(request, 'topic_posts.html', {'topic': topic})
#login_required
def reply_topic(request, pk, topic_pk):
topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk)
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.topic = topic
post.created_by = request.user
post.save()
topic.last_updated = timezone.now()
topic.save()
topic_url = reverse('topic_posts', kwargs={'pk': pk, 'topic_pk': topic_pk})
topic_post_url = '{url}?page={page}#{id}'.format(
url=topic_url,
id=post.pk,
page=topic.get_page_count()
)
return redirect(topic_post_url)
else:
form = PostForm()
return render(request, 'reply_topic.html', {'topic': topic, 'form': form})
#method_decorator(login_required, name='dispatch')
class PostUpdateView(UpdateView):
model = Post
fields = ('message', )
template_name = 'edit_post.html'
pk_url_kwarg = 'post_pk'
context_object_name = 'post'
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(created_by=self.request.user)
def form_valid(self, form):
post = form.save(commit=False)
post.updated_by = self.request.user
post.updated_at = timezone.now()
post.save()
return redirect('topic_posts', pk=post.topic.board.pk, topic_pk=post.topic.pk)
class PostListView(ListView):
model = Post
context_object_name = 'posts'
template_name = 'topic_posts.html'
paginate_by = 20
def get_context_data(self, **kwargs):
session_key = 'viewed_topic_{}'.format(self.topic.pk)
if not self.request.session.get(session_key, False):
self.topic.views += 1
self.topic.save()
self.request.session[session_key] = True
kwargs['topic'] = self.topic
return super().get_context_data(**kwargs)
def get_queryset(self):
self.topic = get_object_or_404(Topic, board__pk=self.kwargs.get('pk'), pk=self.kwargs.get('topic_pk'))
queryset = self.topic.posts.order_by('created_at')
return queryset
And the models.py:
from django.db import models
from django.contrib.auth.models import User
from django.utils.text import Truncator
import math
class Board(models.Model):
name = models.CharField(max_length=30, unique=True)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
def get_posts_count(self):
return Post.objects.filter(topic__board=self).count()
def get_last_post(self):
return Post.objects.filter(topic__board=self).order_by('-created_at').first()
class Topic(models.Model):
subject = models.CharField(max_length=255)
last_updated = models.DateTimeField(auto_now_add=True)
board = models.ForeignKey(Board, on_delete=models.CASCADE, related_name='topics')
starter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='topics')
views = models.PositiveIntegerField(default=0) # <- here
def __str__(self):
return self.subject
def get_page_count(self):
count = self.posts.count()
pages = count / 20
return math.ceil(pages)
def has_many_pages(self, count=None):
if count is None:
count = self.get_page_count()
return count > 6
def get_page_range(self):
count = self.get_page_count()
if self.has_many_pages(count):
return range(1, 5)
return range(1, count + 1)
def get_last_ten_posts(self):
return self.posts.order_by('-created_at')[:10]
class Post(models.Model):
message = models.TextField(max_length=4000)
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(null=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
updated_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='+')
def __str__(self):
truncated_message = Truncator(self.message)
return truncated_message.chars(30)

Related

How do I get and pass context from one form to another based on primary/foreign key fields?

I am currently building a website that will allow the sale of mixing and mastering services. As it is a small set of services, I don't need a shopping cart or any elaborate form of ordering. Instead, I would like a customer details page (which informs my 'Customer' model), an order page where the customer selects what exactly they will be purchasing and uploads any relelvent files (which also informs my 'Order' model), and finally sends the customer to a stripe checkout page.
Currently, the Custome rdetails form is up and running and saving the data to the appropriate database model. Once they click continue, I am struggling to understand how to store the primary key of the Customer instance the user created upon filling out the form, and saving this data in the next form through the foreign key relationship.
Similarly, before being sent to Stripe checkout, I would like to create an 'Order Review' page, reviewing the details of their order. I'm not sure how to pull the primary key of the Order intance that was just created in order to for a Model view on the subsequent page. I believe what I;m missing in order to achieve either of these things is how to get the primary key of the database intsance created by the customer upon submitting the form.
Here is the code relevant to my question, incase I am going about this fundamentally wrong:
models.py
class Customer(models.Model):
first_name = models.CharField(max_length=200, null=False)
last_name = models.CharField(max_length=200, null=False)
phone = models.CharField(max_length=10)
email = models.EmailField(null=False)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.last_name
class Product(models.Model):
MIXMAS = 'Mixing and Mastering Package'
MASO = 'Mastering Only'
FEAT = 'Request a Feature'
TUT = 'Request a Tutor'
NONE = 'Select an option'
PRODUCT_NAME_CHOICES = [
(MIXMAS, 'Mixing and Mastering Package'),
(MASO, 'Mastering Only'),
(FEAT, 'Request a Feature'),
(TUT, 'Request a Tutor'),
(NONE, 'Select an option')
]
name = models.CharField(max_length=100, choices=PRODUCT_NAME_CHOICES, default=NONE)
stripe_product_id = models.CharField(max_length=100)
product_description = models.CharField(max_length=300, null=True)
def __str__(self):
return self.name
class Price(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="prices")
stripe_price_id = models.CharField(max_length=100)
price = models.IntegerField(default=0) # cents
price_description = models.CharField(max_length=300, null=True)
class Meta:
ordering = ['price']
def get_display_price(self):
return "{0:.2f}".format(self.price / 100)
def __str__(self):
return '%s %s %s %s' % ("$", self.price, "-", self.price_description)
class Order(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name='Package Type: ')
price = models.ForeignKey(Price, on_delete=models.CASCADE, verbose_name="Number of stems: ")
cust_requests = models.TextField(max_length=500, null=True, verbose_name='Enter any specific requests here: (Leave blank if none): ')
reference_track = models.CharField(max_length=200, null=True, verbose_name='Reference Track (Leave blank if none): ')
music_file = models.FileField(upload_to='studio_orders/', verbose_name="Upload zipped music file: ")
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
order_date = models.DateTimeField(auto_now_add=True)
forms.py
from .models import Order, Customer, Product, Price
from django import forms
from django.urls import reverse_lazy
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from dynamic_forms import DynamicField, DynamicFormMixin
class OrderForm(DynamicFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
def price_choices(form):
product = form['product'].value()
return Price.objects.filter(product=product)
def initial_price(form):
product = form['product'].value()
return Price.objects.filter(product=product).first()
product = forms.ModelChoiceField(
queryset=Product.objects.all(),
initial=Product.objects.first(),
label= "Select a Product:",
widget= forms.RadioSelect(
attrs={
'hx-get' : 'prices',
'hx-target' : '#prices',
'hx-swap' : 'innerHTML'
}),
required=True,
)
prices = DynamicField(
forms.ModelChoiceField,
queryset=price_choices,
initial=initial_price,
label= "Select a price:"
)
cust_requests = forms.CharField(
label = 'Enter any specific requests here: (Leave blank if none): ',
required=False,
max_length=500
)
reference_track = forms.FileField(
label = 'Upload a reference track, if applicable.',
required=False,
)
music_file = forms.FileField(
label = 'Upload your project here. Please ensure project has been zipped prior to uploading.',
required=True
)
class Meta:
model= Order
fields = ['product', 'prices', 'cust_requests', 'reference_track', 'music_file']
class CustomerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
class Meta:
model = Customer
fields = ['first_name', 'last_name', 'phone', 'email']
urls.py
urlpatterns = [
path('', views.StudiosOverview.as_view(), name='musicstudios'),
path('order-details/', views.orderdetails, name='orderdetails'),
path('customer-details/', views.CustomerDetails.as_view(), name='custdetails'),
path('customer-details/upload', views.custupload, name='custupload'),
path('order-details/prices/', views.prices, name='prices'),
path('order-details/upload', views.orderupload, name='orderupload'),
path('cancel/', CancelView.as_view(), name='cancel'),
path('success/', SuccessView.as_view(), name='success'),
path('create-checkout-session/<int:pk>', CreateCheckoutSessionView.as_view(), name='create-checkout-session')
]
views.py
def orderdetails(request):
form = OrderForm()
context = {'form' : form}
template_name = 'musicstudios/order_details.html'
return render(request, template_name, context)
def prices(request):
form = OrderForm(request.GET)
return HttpResponse(form['prices'])
def custupload(request):
if request.POST:
form = forms.CustomerForm(request.POST, request.FILES)
success_url = reverse_lazy('orderdetails')
print(request.FILES)
if form.is_valid():
form.save()
else:
ctx = {'form' : form}
return HttpResponseRedirect(request, 'musicstudios/customer_details.html', ctx)
return HttpResponseRedirect(success_url)
def orderupload(request):
if request.POST:
form = OrderForm()
success_url = reverse_lazy('create-checkout-session')
if form.is_valid():
form.save()
else:
ctx = {'form' : form}
return render(request, 'musicstudios/order_details.html', ctx)
return reverse_lazy(success_url)
class StudiosOverview(View):
def get_context_data(self, **kwargs):
product = Product.objects.all()
prices = Price.objects.all()
context = super(StudiosOverview, self).get_context_data(**kwargs)
context.update({
"product": product,
"prices": prices
})
return context
def get(self, request):
context = {
'page_headline' : 'Studio Services'
}
context.update(sidebar_context)
return render(request, 'musicstudios/overview.html', context)
class CustomerDetails(CreateView):
form_class = forms.CustomerForm
template_name = 'musicstudios/customer_details.html'
stripe.api_key = settings.STRIPE_SECRET_KEY
class CreateCheckoutSessionView(View):
def post(self, request, *args, **kwargs):
product_id = self.kwargs["pk"]
product = Product.objects.get(id=product_id)
domain = "https://lewnytoonsstudios.com"
if settings.DEBUG:
domain = "http://127.0.0.1:8000"
checkout_session = stripe.checkout.Session.create(
line_items=[
{
# Provide the exact Price ID (for example, pr_1234) of the product you want to sell
'price': product.prices.stripe_price_id,
'quantity': 1,
},
],
mode='payment',
success_url=domain + '/success.html',
cancel_url=domain + '/cancel.html',
automatic_tax={'enabled': True},
)
return JsonResponse({
'id' : checkout_session.id
})
class SuccessView(TemplateView):
template_name = "success.html"
class CancelView(TemplateView):
template_name = "cancel.html"
Relevant HTML templates:
customer_details.html
<span class="flex-auto flex-col">
<form method="post" class="py-2" action="{% url 'custupload' %}" enctype="multipart/form-data"; return False;>
{% csrf_token %}
{{ form|crispy }}
<span class="flex justify-end">
<button class="lewny_button my-4" type="submit">Continue to Order</button>
</span>
</form>
</span>
</div>
order_details.html
<span class="flex-auto flex-col">
<form class="py-2" method="post" action="{% url 'orderupload' %}">
{% csrf_token %}
{{ form|crispy }}
<span class="flex justify-end">
<button class="lewny_button my-4" type="submit">Review Your Order</button>
</span>
</form>
</span>
</div>
I have tried several htmx methods of 'getting' the object but have been unable to achieve anything that works. I considered grabbing the most recent object from the database, but this seemed like a very insecure way to go about the solution.
This would seem a job for session variables. Once your customer is created by your save function, you can grab the id and place it in a session variable for later reference.
if form.is_valid():
customer = form.save()
request.session['customer_id'] = customer.id
You can access this wherever you need, either as request.session['customer_id'] (or request.sessions.get('customer_id') to return None if not set) in a functional view or self.request as above in a class based view.
More info in the docs

AttributeError After processing the view

After Submitting the like button data is successfuly updating with the database but after this step it won't redirecting to the successful url. instead of that it is throwing attribute error. If I use HttpResponseRedirect('/album/') instaed of successful url this error is not comming. Please refer this link for the traceback
models.py
Codes in models.py
class VoteManager(models.Manager):
def get_vote_or_unsaved_blank_vote(self,song,user):
try:
return Vote.objects.get(song=song,user=user)
except ObjectDoesNotExist:
return Vote.objects.create(song=song,user=user)
class Vote(models.Model):
UP = 1
DOWN = -1
VALUE_CHOICE = ((UP, "๐Ÿ‘๏ธ"),(DOWN, "๐Ÿ‘Ž๏ธ"),)
like = models.SmallIntegerField(null=True, blank=True, choices=VALUE_CHOICE)
user = models.ForeignKey(User,on_delete=models.CASCADE)
song = models.ForeignKey(Song, on_delete=models.CASCADE)
voted_on = models.DateTimeField(auto_now=True)
objects = VoteManager()
class Meta:
unique_together = ('user', 'song')
views.py
codes in views.py
class SongDetailView(DetailView):
model = Song
template_name = 'song/song_detail.html'
def get_context_data(self,**kwargs):
ctx = super().get_context_data(**kwargs)
if self.request.user.is_authenticated:
vote = Vote.objects.get_vote_or_unsaved_blank_vote(song=self.object, user = self.request.user)
vote_url = reverse('music:song_vote_create', kwargs={'song_id':vote.song.id})
vote_form = SongVoteForm(instance=vote)
ctx['vote_form'] = vote_form
ctx['vote_url'] = vote_url
return ctx
class SongVoteCreateView(View):
form_class = SongVoteForm
context = {}
def get_success_url(self,**kwargs):
song_id = self.kwargs.get('song_id')
return reverse('music:song_detail', kwargs={'pk':song_id})
def post(self,request,pk=None,song_id=None):
user = self.request.user
song_obj = Song.objects.get(pk=song_id)
vote_obj,created = Vote.objects.get_or_create(song = song_obj,user = user)
vote_form = SongVoteForm(request.POST, instance=vote_obj)
if vote_form.is_valid():
new_vote = vote_form.save(commit=False)
new_vote.user = self.request.user
new_vote.save()
return new_vote
song_detail.html
codes in html page.
<form action="{{vote_url}}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ vote_form.as_p }}
<button class="btn btn-primary" type="submit" >Vote</button>
</form>
Error code
Please refer this link for the traceback
'Vote' object has no attribute 'get'
The post method needs to return an HttpResponse, not a Vote object.
But you shouldn't be defining post in the first place. All that code should go in form_valid.
def form_valid(self, form):
user = self.request.user
song_obj = Song.objects.get(pk=self.kwargs['song_id'])
vote_obj, _ = Vote.objects.get_or_create(song = song_obj, user = user)
form.instance = vote_obj
return super().form_valid(form)
Note, you don't need to check is_valid, and you also don't need to set the user as you've already done that in vote_obj.
You are returning Vote object from post method in your SongVoteCreateView view. You should return Response instead. Django doesn't know what to do with model object returned from a view.

Difference between args=[topic.id] and args=[topic_id] when using the return HttpResponseRedirect(reverse) in Django

I'm following a tutorial from to build a simple blog app with Django.
I have noticed that in the new_entry() view, we need to pass topic_id in agrs when using the reverse function:
def new_entry(request, topic_id):
"""Add a new entry for a particular topic"""
topic = Topic.objects.get(id=topic_id)
if request.method != 'POST':
#No data submitted, create a blank form
form = EntryForm()
else:
#POST data submitted; process data
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.topic = topic
new_entry.save()
return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
context = {'topic': topic, 'form': form}
return render(request, 'learning_logs/new_entry.html', context)
However, when creating the edit_entry() view (that allows users to edit existing entries), we need to pass topic.id
def edit_entry(request, entry_id):
"""Edit an existing entry"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
if request.method != 'POST':
#Initial request, render the form with current entry
form = EntryForm(instance=entry)
else:
#Submit changes, process data
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))
context = {'topic':topic, 'entry':entry, 'form':form}
return render(request,'learning_logs/edit_entry.html', context)
Initially I thought this was a mistake so I used args=[topic_id] in both reverse functions and it worked fine
Later, I decided I wanted to add a title to each entry so I made some minor changes to models.py, migrated those changes to the database and then changed the templates to include {{entry.title}} in them.
Basically, all I did was add this code to models.py
title = models.CharField(max_length=200, default='Add a title')
models.py:
class Topic(models.Model):
"""A topic the user is learning about"""
text = models.CharField(max_length = 200)
date_added = models.DateTimeField(auto_now_add = True)
def __str__(self):
"""Return a string representation of the model"""
return self.text
class Entry(models.Model):
"""A blog post about a particular topic"""
topic = models.ForeignKey(Topic)
title = models.CharField(max_length=200, default='Add a title')
text = models.TextField()
date_added = models.DateTimeField(auto_now_add = True)
class Meta:
verbose_name_plural = 'entries'
def __str__(self):
"""Return a string representation of the model"""
char_numb = len(self.text)
if char_numb > 50:
return self.text[:50] + "..."
else:
return self.text
forms.py:
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text','title']
labels = {'text':'', 'title': ''}
widgets = {'text': forms.Textarea(attrs={'cols': 80})}
After adding these changes, I got the following error when I tried to edit an entry's default title:
NameError at /edit_entry/4/
global name 'topic_id' is not defined
I changed args=[topic_id] to args=[topic.id] in the views.py file edit_entry() view and now it works fine, any idea why this is the case? What difference is there between topic_id and topic.id in this context?
This is the edit_entry.html template in case it makes any difference:
{% extends "learning_logs/base.html" %}
{% block content %}
<h1>{{topic}}
</h1>
<p>Edit your entry</p>
<form action = "{% url 'learning_logs:edit_entry' entry.id %}" method
= 'post'>
{% csrf_token %}
{{ form.as_p }}
<button name = "submit">save changes</button>
</form>
{% endblock content %}
Thanks in advance for any advice
In your first view, you have topic_id from the url and you fetch topic from the database on the first line, so you can use either topic_id or topic in the view.
def new_entry(request, topic_id):
"""Add a new entry for a particular topic"""
topic = Topic.objects.get(id=topic_id)
In the template context for they view, you set topic but not topic_id. Therefore you can only use topic.id in the template.
context = {'topic': topic, 'form': form}
In your second view, you get entry_id from the url and get topic via the entry. You donโ€™t set topic_id anywhere so you must use topic.
def edit_entry(request, entry_id):
"""Edit an existing entry"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic

Django Class BasedView - UpdateView with multiple models and multiple forms

I have a list of users or users/roles type.
I want to be able to click a link to Update their respective profiles users.
When I perform click in Profile user, I should be able to edit the associated data to respective user according to profile that handle each of them. Each data profile is the simple page or template and manage for the same UpdateView class based view.
In detail my scenario is the following:
I have the User (inherit from AbstractBaseUser) model and here I manage the account data of the all users, this mean all data in common to all users roles or types which I want manage in my application such as:
user student
user professor
user executive
This users/roles type have their own model in where I define the respective fields to each of them. So, I have too, StudentProfile ProfessorProfile and ExecutiveProfile models, of this way:
My User model is:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
username = models.CharField(max_length=40, unique=True)
slug = models.SlugField(
max_length=100,
blank=True
)
is_student = models.BooleanField(
default=False,
verbose_name='Student',
help_text='Student profile'
)
is_professor = models.BooleanField(
default=False,
verbose_name='Professor',
help_text='Professor profile'
)
is_executive = models.BooleanField(
default=False,
verbose_name='Executive',
help_text='Executive profile',
)
other fields ...
def get_student_profile(self):
student_profile = None
if hasattr(self, 'studentprofile'):
student_profile = self.studentprofile
return student_profile
def get_professor_profile(self):
professor_profile = None
if hasattr(self, 'professorprofile'):
professor_profile = self.professorprofile
return professor_profile
def get_executive_profile(self):
executive_profile = None
if hasattr(self, 'executiveprofile'):
executive_profile = self.executiveprofile
return executive_profile
def save(self, *args, **kwargs):
user = super(User,self).save(*args,**kwargs)
# Creating an user with student profile
if self.is_student and not StudentProfile.objects.filter(user=self).exists():
student_profile = StudentProfile(user = self)
student_slug = self.username
student_profile.slug = student_slug
student_profile.save()
# Creating an user with professor profile
elif self.is_professor and not ProfessorProfile.objects.filter(user=self).exists():
professor_profile = ProfessorProfile(user=self)
professor_slug = self.username
professor_profile.slug = professor_slug
professor_profile.save()
# Creating an user with executive profile
elif self.is_executive and not ExecutiveProfile.objects.filter(user=self).exists():
executive_profile = ExecutiveProfile(user = self)
executive_slug = self.username
executive_profile.slug = executive_slug
executive_profile.save()
# I have this signal to get the username and assign to slug field
#receiver(post_save, sender=User)
def post_save_user(sender, instance, **kwargs):
slug = slugify(instance.username)
User.objects.filter(pk=instance.pk).update(slug=slug)
The idea behind of these schema is that when I create and user with is_student field checked the StudentProfile model is used for complete their data.
When I create and user with is_professor field checked the ProfessorProfile model is used for complete their data.
When I create and user with is_executive field checked the ExecutiveProfile model is used for complete their data.
The models for each Profile (Student, Professor and Executive) are such as follow:
class StudentProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE
)
slug = models.SlugField(
max_length=100,
blank=True
)
origin_education_school = models.CharField(
_("origin education institute"), max_length=128
)
current_education_school = models.CharField(
_("current education institute"), max_length=128
)
extra_occupation = models.CharField(
_("extra occupation"), max_length=128
)
class ProfessorProfile(models.Model):
CATHEDRAL_PROFESSOR = 'CATHEDRAL'
RESEARCH_PROFESSOR = 'RESEARCH'
INSTITUTIONAL_DIRECTIVE = 'DIRECTIVE'
OCCUPATION_CHOICES = (
(CATHEDRAL_PROFESSOR, 'Cathedral Professor'),
(RESEARCH_PROFESSOR, 'Research Professor'),
(INSTITUTIONAL_DIRECTIVE, 'Institutional Directive'),
)
user = models.OneToOneField(
User,
on_delete=models.CASCADE
)
slug = models.SlugField(
max_length=100,
blank=True
)
occupation = models.CharField(
max_length=255,
blank = False,
)
class ExecutiveProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE
)
slug = models.SlugField(
max_length=100,
blank=True
)
occupation = models.CharField(
max_length=255,
blank = False,
)
enterprise_name = models.CharField(
max_length=255,
blank = False,
)
I have the form to each profile update data of this way in my forms.py:
class UserUpdateForm(forms.ModelForm):
class Meta:
widgets = {
'gender':forms.RadioSelect,
}
fields = ("username", "email", "is_student",
"is_professor", "is_executive",)
model = get_user_model() #My model User
class StudentProfileForm(forms.ModelForm):
class Meta:
model = StudentProfile
fields = ('origin_education_school', 'current_education_school',
'extra_occupation')
class ProfessorProfileForm(forms.ModelForm):
class Meta:
model = ProfessorProfile
fields = ('occupation',)
class ExecutiveProfileForm(forms.ModelForm):
class Meta:
model = ExecutiveProfile
fields = ('occupation', 'enterprise_name', 'culturals_arthistic',
'ecological')
In my class based view AccountSettingsUpdateView I update the data related to the model Users, this mean the Account Data
class AccountSettingsUpdateView(LoginRequiredMixin, UpdateView):
model = get_user_model()
form_class = forms.UserUpdateForm
# success_url = reverse_lazy('dashboard')
context_object_name = 'preferences'
def get_context_data(self, **kwargs):
context = super(AccountSettingsUpdateView, self).get_context_data(**kwargs)
user = self.request.user
if user.is_student:
profile = user.get_student_profile()
context.update({'userprofile': profile})
elif user.is_professor:
profile = user.get_professor_profile()
context.update({'userprofile': profile})
elif user.is_executive:
profile = user.get_executive_profile()
context.update({'userprofile': profile})
return context
The url for the below view is this
url(r"^preferences/(?P<slug>[\w\-]+)/$",
views.AccountSettingsUpdateView.as_view(),
name='preferences'
),
This view AccountSettingsUpdateView works OK.
[02/Apr/2017 23:51:17] "GET /accounts/preferences/luisa/ HTTP/1.1" 200 18092
And, in my other view and only in this view I am updating the data related to the profile of each one of these users. this mean the profiles described above:
class AccountProfilesView(LoginRequiredMixin, UpdateView):
# When I ask for user with Student Profile
model = StudentProfile
form_class = forms.StudentProfileForm
# sending the form to ProfessorProfile
second_form_class = forms.ProfessorProfileForm
# sending the form to ExecutiveProfile
third_form_class = forms.ExecutiveProfileForm
success_url = reverse_lazy('dashboard')
template_name = 'accounts/student_form.html'
def get_context_data(self, **kwargs):
context = super(AccountProfilesView, self).get_context_data(**kwargs)
user = self.request.user
if 'form' not in context:
context['form'] = self.form_class(self.request.GET,
instance=user)
if 'form2' not in context:
context['form2'] = self.second_form_class(self.request.GET,
instance=user)
'''
if 'form3' not in context:
context['form3'] = self.third_form_class(self.request.GET,
instance=user)
'''
if user.is_student:
profile = user.get_student_profile()
context.update({'userprofile': profile})
elif user.is_professor:
profile = user.get_professor_profile()
context.update({'userprofile': profile})
elif user.is_executive:
profile = user.get_executive_profile()
context.update({'userprofile': profile})
return context
And the url to this AccountProfilesView view is this
url(r"^profile/(?P<slug>[\w\-]+)/$",
views.AccountProfilesView.as_view(
model=ProfessorProfile),
name='profile'
),
Note that in the url i am passing the ProfessorProfile model like parameter, despite that in the view AccountProfilesView body, I am defining the StudentProfile model, but what happen is that in the url the model = ProfessorProfile override to the model = StundentProfile which came from the view.
In this moment if I am use the luisa user with profile StudentProfile and I going to the url http://localhost:8000/accounts/profile/luisa/
the url is not found:
[03/Apr/2017 01:20:25] "GET /accounts/profile/luisa/ HTTP/1.1" 404 1771
Not Found: /accounts/profile/luisa/
But If I remove the attribute model=ProfessorProfile that I am passing like parameter in the URL, this mean that my url stay so:
url(r"^profile/(?P<slug>[\w\-]+)/$", views.AccountProfilesView.as_view(), name='profile')
The url http://localhost:8000/accounts/profile/luisa/ is OK
[03/Apr/2017 01:28:47] "GET /accounts/profile/luisa/ HTTP/1.1" 200 4469
This happen because in the view persist the model=StudentProfile attribute.
Until this point, if I am use an user with ProfessorProfile named david and I am going to their profile URL, this is not found
Not Found: /accounts/profile/david/
[03/Apr/2017 01:30:19] "GET /accounts/profile/david/ HTTP/1.1" 404 1769
But I add again the attribute model=ProfessorProfile that I am passing like parameter in the URL, such as mentioned above, the url of david profile ProfessorProfile is OK.
[03/Apr/2017 01:33:11] "GET /accounts/profile/david/ HTTP/1.1" 200 4171
The same inconvenient I have with the ExecutiveProfile user type.
According to the previous behavioral, is of this way, which, I am defining the view to ask for the user type roles and render their respective form.
But the inconvenient is that in my view AccountProfilesView I cannot pass or indicate more than One model.
I am trying indicate one second model in my AccountProfilesView of this way:
class AccountProfilesView(LoginRequiredMixin, UpdateView):
model = StudentProfile
form_class = forms.StudentProfileForm
second_form_class = forms.ProfessorProfileForm
third_form_class = forms.ExecutiveProfileForm
#success_url = reverse_lazy('dashboard')
template_name = 'accounts/student_form.html'
def get_context_data(self, **kwargs):
context = super(AccountProfilesView, self).get_context_data(**kwargs)
# Indicate one second model
context['professor_profile'] = ProfessorProfile
But the result is the same
In summary my question is:
In the UpdateView class based view ...
How to can I work with multiple model (more precisely three models StudentProfile, ProfessorProfile and ExecutiveProfile) to can render their respective model forms with the order of access to each profile page user?
I want can perform this wuth any amount of ProfileUser that I have.
I unknown if my schema User and ProfileUser model are good of if there is some better alternative of address this challenge.
UPDATE
According to the answer of #Ma0 Collazos their solution works great.
In this moment the goal is can make a combination of the different profiles, and can render the forms of each profile. So, if an user have a is_professor and is_executive profile, can show in their profile view (AccountProfilesView) their forms respective, this mean that when I go to profile user, can I see the fields the forms professor and the fields the form executive
With the order to get this purpose, I add the scenario in where an user have the combination of profiles in my AccountProfilesView such as follow:
class AccountProfilesView(LoginRequiredMixin, UpdateView):
# All users can access this view
model = get_user_model()
template_name = 'accounts/profile_form.html'
fields = '__all__'
def get_context_data(self, **kwargs):
context = super(AccountProfilesView, self).get_context_data(**kwargs)
user = self.request.user
if not self.request.POST:
if user.is_student:
profile = user.get_student_profile()
context['userprofile'] = profile
context['form_student'] = forms.StudentProfileForm()
elif user.is_professor:
profile = user.get_professor_profile()
context['userprofile'] = profile
context['form_professor'] = forms.ProfessorProfileForm()
elif user.is_executive:
profile = user.get_executive_profile()
context['userprofile'] = profile
context['form_executive'] = forms.ExecutiveProfileForm()
elif user.is_student and user.is_professor and user.is_executive:
student_profile = user.get_student_profile()
professor_profile = user.get_professor_profile()
executive_profile = user.get_executive_profile()
context['student_profile'] = student_profile
context['professor_profile'] = professor_profile
context['executive_profile'] = executive_profile
context['form_student'] = forms.StudentProfileForm()
context['form_professor'] = forms.ProfessorProfileForm()
context['form_executive'] = forms.ExecutiveProfileForm()
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
user = self.request.user
if user.is_student:
context['form_student'] = forms.StudentProfileForm(self.request.POST)
elif user.is_professor:
context['form_professor'] = forms.ProfessorProfileForm(self.request.POST)
elif user.is_executive:
context['form_executive'] = forms.ExecutiveProfileForm(self.request.POST)
elif user.is_student and user.is_professor and user.is_executive:
context['form_student'] = forms.StudentProfileForm(self.request.POST)
context['form_professor'] = forms.ProfessorProfileForm(self.request.POST)
context['form_executive'] = forms.ExecutiveProfileForm(self.request.POST)
return super(AccountProfilesView, self).post(request, *args, **kwargs)
def form_valid(self, form):
context = self.get_context_data(form=form)
user = self.request.user
user = form.save()
if user.is_student:
student = context['form_student'].save(commit=False)
student.user = user
student.save()
elif user.is_professor:
professor = context['form_professor'].save(commit=False)
professor.user = user
professor.save()
elif user.is_executive:
executive = context['form_executive'].save(commit=False)
executive.user = user
executive.save()
elif user.is_student and user.is_professor and user.is_executive:
student = context['form_student'].save(commit=False)
student.user = user
student.save()
professor = context['form_professor'].save(commit=False)
professor.user = user
professor.save()
executive = context['form_executive'].save(commit=False)
executive.user = user
executive.save()
return super(AccountProfilesView, self).form_valid(form)
And in my profile form template, I have the following small logic in which the render of the form to each profile of a separate way works, but when I ask if an user have the three profiles is_student, is_professor and is_executive such as is code section at the end of my template, and I going to profile page of this user, the three forms does not renderized:
<form method="POST">
{% csrf_token %}
{% if userprofile.user.is_student %}
{% bootstrap_form form_student %}
{% elif userprofile.user.is_professor %}
{% bootstrap_form form_professor %}
{% elif userprofile.user.is_executive %}
{% bootstrap_form form_executive %}
{% elif userprofile.user.is_student and
userprofile.user.is_professor and
userprofile.user.is_executive %}
{% bootstrap_form form_student %}
{% bootstrap_form form_professor %}
{% bootstrap_form form_executive %}
{% endif %}
<input type="submit" value="Save Changes" class="btn btn-default">
</form>
Why my three forms, is not possible show them in one form?
Make class AccountProfilesView to have an attribute model = get_user_model(), so all users can access this view.
In your get_context_data method define what form is going to be render and make sure to fill this forms with data entered in a POST method
# NoQA
if not self.request.POST:
if user.is_student:
context['form_student'] = forms.StudentProfileForm()
elif user.is_professor:
context['form_professor'] = forms.ProfessorProfileForm()
...
else:
if user.is_student:
context['form_student'] = forms.StudentProfileForm(self.request.POST)
elif user.is_professor:
context['form_professor'] = forms.ProfessorProfileForm(self.request.POST)
...
Then override the form_valid method to save the corresponding forms
def form_valid(self, form):
context = self.get_context_data(form=form)
user = form.save()
# NoQA
if user.is_student:
student = context['form_student'].save(commit=False)
student.user = user
student.save()
elif user.is_professor:
professor = context['form_professor'].save(commit=False)
professor.user = user
professor.save()
...
Answer based on this post

Success_url in django RedirectView

does somebody know, can I use SuccessMessageMixin with RedirectView? Because when I use it in my views:
class CarRentView(SuccessMessageMixin,RedirectView):
success_message = "Well done!"
permanent = False
query_string = True
model = Car
def get_redirect_url(self, *args, **kwargs):
car = get_object_or_404(Car, pk=kwargs['pk'])
car.rent=True
car.save()
return reverse('cars')
there is nothing happend.
And I've got another question - is there any way to 'block' car, which is rent for next user and make a queue of people who want the same car?
SuccessMessageMixin for 'FormView' classes, RedirectView does not have forms functionality
Second question more complex
I think you need to do some like (not tested)
models.py
class Car(models.Model):
...
class CarQueue(models.Model):
car = models.ForeignKey(Car)
user = models.ForeignKey(User)
updated_at = models.DateTimeField(auto_now=True)
state = models.PositiveSmallIntegerField(default=1)
# 1 - user rent, 2 - user wait, 0 - archive
...
class Meta:
unique_together = [['car', 'user']]
ordering = ['state', 'updated_at']
views.py
class CarRentView(UpdateView):
model = CarQueue
fields = ['car']
def get_object(self):
return self.object
def get_success_url(self):
# return url according object.state
def form_valid(self, form):
# registered users can rent
form.instance.user = self.request.user
qset = self.model.objects.filter(car=self.car, state__gt=0)
if self.object:
qset = qset.exclude(pk=self.object.pk)
form.instance.state = 1 if qset.exists() else 2
return super(..).form_valid(form)
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs)
self.car = get_object_or_404(self.model, pk=kwargs['car_pk'])
try:
self.object = self.model.objects.get(car=car, user=request.user)
except ObjectDoesNotExist:
self.object = None
return super(..).dispatch(..)
car_rent_view.html
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit">
</form>
urls.py
url(r'^rent/(?P<car_pk>[^\/]+)/', views.CartRentView.as_view(), name='CartRentView'),