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'),
Related
Disclaimer: I'm just a novice trying to learn Django
Hello, I'm trying to refactor my code and modify all the views that I have created to be Class Based Views.
I have an issue loading a form with DeleteView that is showing the data and at the same time is disabled.
I have some success and the only thing that I cannot figure out how to do is to show the data instead of the message that appears now "<django.db.models.query_utils.DeferredAttribute object at 0x04725628>"
+models.py:
class Note(models.Model):
title = models.CharField(max_length=30)
image_url = models.URLField()
content = models.TextField()
owner = models.ForeignKey(Profile, default=8, on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse(self.pk)
def __str__(self):
return f'{self.title}'
+forms.py
class NoteForm(forms.ModelForm):
class Meta:
model = Note
exclude = ('owner',)
class DeleteNoteForm(NoteForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (_, field) in self.fields.items():
field.widget.attrs['readonly'] = True
field.widget.attrs['disabled'] = True
+views.py
class DeleteNoteView(DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['form'] = self.form_class(instance=self.model)
return data
+urls.py
path('delete/<int:pk>/', views.DeleteNoteView.as_view(), name='delete note'),
+template
<!--note delete data form-->
<div class="form">
<form method="POST">
{{ form }}
{% csrf_token %}
<input type="submit" value="Delete"/>
</form>
</div>
<!--end note delete data form-->
If I use my view it works fine, but I want to modify it.
def delete_note(request, pk):
note = Note.objects.get(pk=pk)
if request.method=='GET':
note_form = DeleteNoteForm(instance=note)
context = {
'note_form': note_form
}
return render(request, 'note-delete.html', context)
else:
note.delete()
return redirect('home page')
Could someone tell me where I'm wrong and how I can fix it or at least provide me a link with information to understand why this is happening?
You are passing a reference to the model class in your DeleteNoteView, whereas you should use the object that is removed, so:
class DeleteNoteView(DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
# user self.object instead of self.model ↓
data['form'] = self.form_class(instance=self.object)
return data
I would also advise to filter the QuerySet such that it is impossible for another user (a user that is not the owner of a Note to remove that Note:
from django.contrib.auth.mixins import LoginRequiredMixin
class DeleteNoteView(LoginRequiredMixin, DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
owner=self.request.user
)
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['form'] = self.form_class(instance=self.object)
return data
I wanto to display the current user in the form before submitting.
views.py
class PostEncabezadoReporte(LoginRequiredMixin, CreateView):
login_url = '/login/'
redirect_field_name = 'redirect_to'
form_class = PostEncabezadoReporteForm
template_name = "crear_reporte.html"
def form_valid(self, form):
object = form.save(commit=False)
object.user = self.request.user
object.startweek, object.endweek = self.weekdatetimeconverter(
object.semana)
object.folio = self.getfolio(
object.user, object.semana, object.tipo_reporte)
self.validar_unico = self.reporte_unico(
object.user, object.semana, object.cliente)
if self.validar_unico == 0:
object.save()
else:
return self.form_invalid(form)
return super(PostEncabezadoReporte, self).form_valid(form)
forms.py
class PostEncabezadoReporteForm(forms.ModelForm):
class Meta:
model = EncabezadoReporte
fields = ('user', 'tipo_reporte', 'tipo_gasto', 'cliente',
'semana', 'folio')
widgets = {'semana': forms.DateInput(attrs={'type': 'week'}),
}
I alreayd tried to override the init in the form and is not working, I can select the user in the field but I want it to be displayed at init.
I have tried to override the init in the form but I had a problem, I was missing a line after the super, this is an example of another form init that I did:
def __init__(self, *args, **kwargs):
self.carro = kwargs.pop('encabezado')
super(AgregarGastoReporte, self).__init__(*args, **kwargs)
self.fields['encabezado'].initial = self.carro
I tried this in my modelform:
class Ledgerform(forms.ModelForm):
class Meta:
model = ledger1
fields = ('name', 'group1_Name')
def __init__(self, User, Company, *args, **kwargs):
self.User = kwargs.pop('User', None)
self.Company = kwargs.pop('Company', None)
super(Ledgerform, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs = {'class': 'form-control',}
self.fields['group1_Name'].queryset = group1.objects.filter(User= self.User,Company = self.Company)
In my views.py I have done something like this:
class ledger1ListView(LoginRequiredMixin,ListView):
model = ledger1
paginate_by = 15
def get_queryset(self):
return ledger1.objects.filter(User=self.request.user, Company=self.kwargs['pk'])
class ledger1CreateView(LoginRequiredMixin,CreateView):
form_class = Ledgerform
def form_valid(self, form):
form.instance.User = self.request.user
c = company.objects.get(pk=self.kwargs['pk'])
form.instance.Company = c
return super(ledger1CreateView, self).form_valid(form)
I want to perform the the same query that I have passed in my ledger1ListView by using queryset in my modelform but my kwargs.pop is not returning the current user or the company...
This is my models.py:
class ledger1(models.Model):
User = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True)
Company = models.ForeignKey(company,on_delete=models.CASCADE,null=True,blank=True,related_name='Companys')
name = models.CharField(max_length=32)
group1_Name = models.ForeignKey(group1,on_delete=models.CASCADE,blank=True,null=True)
Do any one know what I am doing wrong in my code?
Thank you in advance
You can override the FormMixin.get_form_kwargs [Django-doc] in your view, that it constructs a dictionary with the parameters necessary to initialize the form, like:
class ledger1CreateView(LoginRequiredMixin,CreateView):
form_class = Ledgerform
def get_form_kwargs(self):
data = super(ledger1CreateView, self).get_form_kwargs()
data.update(
User=self.request.User,
Company=company.objects.get(pk=self.kwargs['pk'])
)
return data
The form_valid function is called after the form is constructed, validated and appears to be valid. Typically it is used to redirect the user to the "success page".
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)
Using inlineformset_factory I am able to add / remove phone numbers related to a single customer. Only problem is, I want to require at least 1 valid phone number for each customer.
Here is some demo code:
Models:
class Customer( models.Model ):
name = models.CharField( max_length=255 )
class PhoneNumber( models.Model ):
customer = models.ForeignKey( Customer )
number = models.CharField( max_length=10 )
Forms:
class CustomerForm( ModelForm ):
class Meta:
model = Customer
fields = ['name']
class PhoneNumberForm( ModelForm ):
class Meta:
model = PhoneNumber
fields = ['number']
Ok, so that's pretty straight forward.
Then in my view:
class Create( View ):
template_name = 'path_to_template'
CustomerForm = forms.CustomerForm
PhoneNumberFormSet = inlineformset_factory (
parent_model = Customer,
model = PhoneNumber,
form = PhoneNumberForm,
extra = 1,
)
def get(self, request):
# Return empty forms
context = {
'customer_form': self.CustomerForm,
'phone_number_formset': self.PhoneNumberFormSet
}
render( request, self.template_name, context)
def post(self, request):
this_customer_form = self.CustomerForm( request.POST )
if this_customer_form.is_valid():
new_customer.save(commit=False)
this_phone_number_formset = self.PhoneNumberFormSet(request.POST, instance=new_customer)
if this_phone_number_formset.is_valid():
new_customer.save()
this_phone_number_formset.save()
return HttpResponseRedirect(reverse_lazy('customer-detail', kwargs={'pk': new_customer.pk}))
# Something is not right, show the forms again
this_phone_number_formset = self.PhoneNumberFormSet(request.POST)
context = {
'customer_form': this_customer_form,
'phone_number_formset': this_phone_number_formset
}
render( request, self.template_name, context)
You get the point I think. Same thing for the Edit/Update view of the customer. Only then the forms are prepopulated.
At this point all I need is a way to require at least 1 valid PhoneNumber per Customer.
I found something like:
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
from https://stackoverflow.com/questions/2406537/django-formsets-make-first-required
but it doesnt seem to work when I apply this on a BaseInlineFormSet class.
Django 1.7 seems to answer my wishes, but not for a InlineModelFormSet so far..
Any ideas?
If you just want to set the minimum or maximum, you can set them directly in inlineformset_factory, here's my code for minimum of one entry
from django.forms import inlineformset_factory
SubUnitFormSet = inlineformset_factory(
Unit, SubUnit, form=SubUnitForm, min_num=1, validate_min=True, extra=0)
You need to properly handle this in your view. I'm using CBV and this is my code for your reference
class UnitCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
permission_required = "core.add_unit"
model = Unit
form_class = UnitForm
template_name = 'core/basic-info/unit_form.html'
success_url = reverse_lazy('core:units')
success_message = _("%(code)s was added successfully")
def get_context_data(self, **kwargs):
data = super(UnitCreateView, self).get_context_data(**kwargs)
if self.request.POST:
data['subunits'] = SubUnitFormSet(self.request.POST, )
else:
data['subunits'] = SubUnitFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
subunits = context['subunits']
with transaction.atomic():
if subunits.is_valid():
self.object = form.save()
subunits.instance = self.object
subunits.save()
else:
return self.render_to_response(self.get_context_data(form=form))
return super(UnitCreateView, self).form_valid(form)
Thank you kezabella ( django irc ).
Seems I found a solution by subclassing BaseInlineFormset:
class RequiredFormSet(BaseInlineFormSet):
def clean(self):
for form in self.initial_forms:
if not form.is_valid() or not (self.can_delete and form.cleaned_data.get('DELETE')):
return
for form in self.extra_forms:
if form.has_changed():
return
raise ValidationError("No initial or changed extra forms")
Btw, these validation errors do not show up in {{ formset.error }} but in:
{{ formset.non_form_errors }}