With Django 1.4 and Allauth, I'm trying to have 2 different signup pages. When I say different, I mean 2 different URLs and 'html layouts'.
Here's what I did so far. It 'works', but it doesn't pass the 'is_alternate_template' variable to the HTML template:
In urls.py:
url(r'^accounts/', include('allauth.urls')),
url(r'^abc/alternate-signup/?$','project.views.alternate_signup'),
in views.py:
def alternate_signup(request):
from allauth.account import views as account_views
is_alternate_template = True
return account_views.signup(request, locals(), context_instance=RequestContext(request))
in templates/account/signup.html
{% if is_alternate_template %}
display the alternate layout of the signup page
{% else %}
display the 'standard' layout of the signup page
{% endif %}
In the Allauth module, here's what the signup view looks like in account/views (this is the view I overrode from the Allauth module, not something I wrote myself):
class SignupView(RedirectAuthenticatedUserMixin, CloseableSignupMixin, FormView):
template_name = "account/signup.html"
form_class = SignupForm
redirect_field_name = "next"
success_url = None
def get_success_url(self):
# Explicitly passed ?next= URL takes precedence
ret = (get_next_redirect_url(self.request,
self.redirect_field_name)
or self.success_url)
return ret
def form_valid(self, form):
user = form.save(self.request)
return complete_signup(self.request, user,
app_settings.EMAIL_VERIFICATION,
self.get_success_url())
def get_context_data(self, **kwargs):
form = kwargs['form']
form.fields["email"].initial = self.request.session.get('account_verified_email', None)
ret = super(SignupView, self).get_context_data(**kwargs)
login_url = passthrough_next_redirect_url(self.request,
reverse("account_login"),
self.redirect_field_name)
redirect_field_name = self.redirect_field_name
redirect_field_value = self.request.REQUEST.get(redirect_field_name)
ret.update({"login_url": login_url,
"redirect_field_name": redirect_field_name,
"redirect_field_value": redirect_field_value })
return ret
signup = SignupView.as_view()
What your need to do is to create a subclass of SignupView in your views.py it can be really simple.
class SignupViewExt(SignupView):
template_name = "account/signup_alternate.html"
Yes, as simple as that because the template is the only thing you need to change. Then change your urls.py as
url(r'^abc/alternate-signup/?$',project.views.SignupViewExt.as_view()),
if you want to pass additional parameters:
url(r'^abc/alternate-signup/?$',project.views.SignupViewExt.as_view(param='some value')),
that would then be available to SignupViewExt as self.param.
Related
Directly how can I pass the context in the same time inside the class to my html pages?
the class is an updateView but I need the context in the page too:
class GL_PRUP(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
model = Gas_liftM
template_name = 'Home/WELLINFO/W_lift/GL_liftUPARMTS.html'
form_class = Gas_liftFORM
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
WeelN = self.get_object()
GLParameters = Wcomlption.objects.filter(WellID__exact=WeelN)[0]
print(GLParameters)
context={'GLParameters': GLParameters,} # need to pass this variable to my html page?
if self.request.user== WeelN.author:
return (True, context)
else:
return False
Every thing works fine in the update html page but only can't get the GLParameters??
{% if GLParameters %}
{{ GLParameters.WellID }} it works Tyaeb {{ GLParameters.Mndrn2 }}
{% else %}
nothing passes and happens
{% endif %}
Thank you in advance
UpdateView inherits from multiple mixins and one of them is SingleObjectMixin that inherits from ContextMixin and thus you can override get_context_data method in order to extend the context with what you need.
Should be something like (untested):
...
def get_context_data(self, **kwargs):
context = super().context(**kwargs)
WeelN = self.get_object()
GLParameters = Wcomlption.objects.filter(WellID__exact=WeelN).first()
context.update(
{
'GLParameters': GLParameters,
'author': self.request.user == WeelN.author
}
)
return context
I know that I can use decorators such as #login_required and #permission_required() or enclose a view in a function like login_required() to redirect a user to sort of provide more information (log in in that case). On success the user is redirected to the URL he was trying to access in the first place (automatically using the logic ?next=/ in the URL).
Now I would like to apply the ?next=/ logic to another case. My user is logged in and wants to claim a piece on the website. To successfully do this he must have provided his address. In the view I check if all the address fields are not empty. If one of the fields is empty I redirect the user to a default UpdateView (form). If the user filled in the fields (clicks the submit button) I would like to redirect him to the URL he came from (trying to claim the piece). Due to this redirect the process of checking if all address fields are not empty would start over and if it succeeds this time the piece is claimed.
How does the ?next=/ logic apply in such a case?
views.py
from django.views.generic import RedirectView
from django.http import QueryDict
class ClaimRedirectView(RedirectView):
REQUIRED_FIELDS = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'state_province', 'location', 'country']
permanent = False
def get_redirect_url(self, *args, **kwargs):
claimant = get_object_or_404(Creator, user=self.request.user)
missing_fields = [fields.name for fields in claimant._meta.get_fields(include_hidden=False) if fields.name in self.REQUIRED_FIELDS and not getattr(claimant, fields.attname, None)]
if not missing_fields:
return reverse('my-claimed')
messages.info(self.request, 'Please fill in your complete address to proceed')
next = self.request.get_full_path()
path = reverse('creator-update', kwargs={'slug': claimant.slug})
#q = QueryDict('next=%s' % next)
q = 'next=' + next
return '%s?%s' % (path, q)
class CreatorUpdate(LoginRequiredMixin, UpdateView):
model = Creator
slug_field = 'slug'
fields = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'location', 'state_province', 'country']
# these two methods only give access to the users own profile but not the others
def user_passes_test(self, request):
if request.user.is_authenticated:
self.object = self.get_object()
return self.object.user == request.user
return False
def dispatch(self, request, *args, **kwargs):
if not self.user_passes_test(request):
return redirect_to_login(request.get_full_path())
return super(CreatorUpdate, self).dispatch(request, *args, **kwargs)
urls.py
path('claim/<uuid:pk>', login_required(views.ClaimRedirectView.as_view()), name='claim')
path('creator/<slug:slug>/update/', views.CreatorUpdate.as_view(), name='creator-update')
creator_form.html
{% extends "base_generic.html" %}
{% block content %}
{% if messages %}
{% for message in messages %}
<p{% if message.tags %} class="{{ message.tags }}"{% endif %}><strong>{{ message }}</strong></p>
{% endfor %}
{% endif %}
<form action="" method="post">
{% csrf_token %}
{{ form.as_ul }}
<input type="submit" value="Submit">
</form>
{% endblock %}
When implementing the above ClaimRedirectView as suggested by # I get to the form to fill in more information and see the correct url (having the next logic). But when filling in the form I do not get directed to the next part of the url. May this have something to do with the form (generic UpdateView) itself?
Since the view redirects the request a better alternative will be to use a RedirectView and next isn't added to the url config hence the error.
It should be a querystring and the CreatorUpdate.get_absolute_url should be able to retrieve the param from the GET dict. i.e request.GET.get('next')
from django.http import QueryDict
class ClaimRedirectView(RedirectView):
REQUIRED_FIELDS = ['first_name', 'last_name', ...]
permanent = False
def get_redirect_url(self, *args, **kwargs):
claimant = get_object_or_404(Creator, user=self.request.user)
missing_fields = [
f.name for fields in claimant._meta.get_fields(include_hidden=False)
if f.name in REQUIRED_FIELDS and not getattr(claimant, f.attname, None)
]
if not missing_fields:
return reverse('my-claimed')
messages.info(request, 'Please fill in your complete address to proceed')
next = request.get_full_path()
path = reverse('creator-update', kwargs={'slug': claimant.slug}))
q = QueryDict('next=%s' % next)
return '%s?%s' % (path, q.urlencode())
And the urls.py with a configuration the <next:next> isn't a kwarg i.e not a named group.
path('creator/<slug:slug>/update/', views.CreatorUpdate.as_view(), name='creator-update')
path('claim/<uuid:pk>', login_required(views.claim), name='claim')
To use the next as the redirect path on CreatorUpdate view.
class CreatorUpdate(LoginRequiredMixin, UpdateView):
model = Creator
slug_field = 'slug' # <-- This is already the default
fields = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'location', 'state_province', 'country']
# This should be done using the `get_queryset`
def get_queryset(self):
qs = super().get_queryset()
# if request.user.is_authenticated: # Using the LoginRequiredMixin mixin users will already be authenticated.
return qs.filter(user=self.request.user)
def form_valid(self, form):
next = self.request.GET.get('next')
if next:
return redirect(next)
return super().form_valid(form)
Just like in the native login_required, you save the path to the next GET parameter:
def claim(request, pk):
claimant = Creator.objects.get(user=request.user)
if claimant.first_name != None and claimant.last_name != None and claimant.street != None and claimant.street_number != None and claimant.zip_code != None and claimant.location != None and claimant.state_province != None::
(...)
else:
next = request.get_full_path()
return HttpResponseRedirect(reverse('creator-update', kwargs={'slug': claimant.slug, 'next': next}))
and then in the creator-update view you check if there is next and redirect to that after a successful update. You might store the URL in a hidden input field.
I suggest checking the Django internals regarding this, specifically django.contrib.auth.user_passes_test, django.contrib.auth.views.redirect_to_login and django.contrib.auth.views.LoginView. It's no magic at all, everything there is very straightforward. For LoginView you should be familiar with class-based views.
Putting together parts of my original code and suggestions from #jackotonye I got it working with the following code:
views.py
from django.contrib import messages
from django.views.generic import RedirectView
class ClaimRedirectView(RedirectView):
REQUIRED_FIELDS = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'state_province', 'location', 'country']
permanent = False
def get_redirect_url(self, pk, *args, **kwargs):
claimant = get_object_or_404(Creator, user=self.request.user)
missing_fields = [fields.name for fields in claimant._meta.get_fields(include_hidden=False) if fields.name in self.REQUIRED_FIELDS and not getattr(claimant, fields.attname, None)]
if not missing_fields:
piece_instance = PieceInstance.objects.get(pk=pk)
piece_instance.claimant = self.request.user
piece_instance.date_claimed = datetime.date.today()
piece_instance.status = 'c'
piece_instance.save()
return reverse('my-claimed')
messages.info(self.request, 'Please fill in your complete address to proceed')
next = self.request.get_full_path()
path = reverse('creator-update', kwargs={'slug': claimant.slug})
q = 'next=' + next
return '%s?%s' % (path, q)
class CreatorUpdate(LoginRequiredMixin, UpdateView):
model = Creator
slug_field = 'slug'
fields = ['first_name', 'last_name', 'street', 'street_number', 'zip_code', 'location', 'state_province', 'country']
# these two methods only give access to the users own profile but not the others
def user_passes_test(self, request):
if request.user.is_authenticated:
self.object = self.get_object()
return self.object.user == request.user
return False
def dispatch(self, request, *args, **kwargs):
if not self.user_passes_test(request):
return redirect_to_login(request.get_full_path())
return super(CreatorUpdate, self).dispatch(request, *args, **kwargs)
def get_success_url(self):
if 'next' in str(self.request.META.get('HTTP_REFERER')):
return self.request.GET.get('next', '/')
return reverse_lazy('creator-detail', kwargs={'slug': self.object.slug})
urls.py
path('claim/<uuid:pk>', login_required(views.ClaimRedirectView.as_view()), name='claim')
path('creator/<slug:slug>/update/', views.CreatorUpdate.as_view(), name='creator-update')
The two crucial steps to success regarding the problem I had were to actually fill in and save the arguments in the ClaimRedirectView at if not missing_fields: and defining the get_success_url method in CreatorUpdate. The get_success_url manually checks if the string next is in the url and redirects accordingly.
I'm trying to write a DeleteView for deleting posts without getting displayed a confirmation page.
Del - delete button. How can I delete the object immediately?
urls.py:
urlpatterns = [
# url(r'^$', views.index, name='index'),
url(
r'^feed$',
views.FeedView.as_view(),
name='feed'
),
url(r'^summary(?P<pk>\w{0,50})',
views.SummaryCreate.as_view(),
name='summary'),
url(r'^summary(?P<user_id>\w{0,50})/(?P<pk>\w{0,50})/',
views.SummaryDelete.as_view(),
name='delete_summary'),
url(r'^dashboard$',
permission_required('reed.view_dashboard')
(views.DashboardListView.as_view()),
name='dashboard'),
url(r'^dashboard/(?P<pk>\w{0,50})',
permission_required('reed.view_dashboard')
(views.DashboardUpdate.as_view()),
name='review_summary'),
]
views.py
class SummaryCreate(LoginRequiredMixin, generic.CreateView):
template_name = 'template/summary_list.html'
model = Summary
form_class = AddUrlForm
login_url = '/login_page/login/'
redirect_field_name = 'login_page'
def get_context_data(self, **kwargs):
return dict(
super(SummaryCreate, self).get_context_data(**kwargs),
summary_list=reversed(Summary.objects.filter(user_id=self.kwargs['pk']).reverse())
)
def get_success_url(self):
return reverse('summary', args=(self.request.user.id.hex,))
def form_valid(self, form):
print(self.request.user.id.hex)
url_inst = form.save(commit=False)
keywords_inst = Keywords
article = Article(form.cleaned_data['url'], language='en')
article.download()
article.parse()
title = article.title
print(title)
try:
image = article.top_image
print(image)
except Exception:
image = ''
article.nlp()
try:
keywords = article.keywords
print(keywords)
except Exception:
keywords = 'Sorry,no,keywords,found'
try:
summary = article.summary
print(summary)
except Exception:
summary = 'Sorry, no summmary found'
try:
publish_date = article.publish_date
publish_date = publish_date.date()
print(publish_date)
except Exception:
publish_date = '1900-01-01'
user = User.objects.get(id=self.request.user.id.hex)
url_inst.url=form.cleaned_data['url']
url_inst.image=image
url_inst.title=title
url_inst.summary=summary
url_inst.date=publish_date
url_inst.user_id=user
url_inst.save()
summary = Summary.objects.get(url=form.cleaned_data['url'])
#
for keyword in keywords:
new_keyword = keywords_inst(keyword=keyword, keyword_id=summary)
new_keyword.save()
#
return super(SummaryCreate, self).form_valid(form)
class SummaryDelete(SummaryCreate, generic.DeleteView):
model = Summary
pk_url_kwarg = 'pk'
slug_url_kwarg = 'pk'
def get_success_url(self):
return reverse('summary', args=(self.request.user.id.hex,))
def dispatch(self, request, *args, **kwargs):
return super(SummaryDelete, self).dispatch(request, *args, **kwargs)
template.html:
<form action="{% url 'delete_summary' user.id.hex summary.id.hex %}" method="post">{% csrf_token %}
<h3>
<input type="submit" class="delete" aria-hidden="true" value="X">
{{summary.title}}
</h3>
</form>
I have 2 classes in one template: 1 for displaying all posts and adding new posts and second for deleting, but deleting only redirect me on page, that I provide for DeleteView.
DeleteView:
A view that displays a confirmation page and deletes an existing
object. The given object will only be deleted if the request method is
POST. If this view is fetched via GET, it will display a confirmation
page that should contain a form that POSTs to the same URL.
You need a form element in order to send a POST request.
template.html:
<form id="my_form" method="post" action="{% url 'delete_summary' user.id.hex summary.id.hex %}">
{% csrf_token %}
</form>
Del
I have created a ModelForm (Django 1.9.1) for my application settings page. It should work in the following manner:
Settings page should have multiple text fields with the value from database.
If I change any field and then press "Save" button - it should update same fields in DB, not add new.
Model:
class Settings(models.Model):
pkey_path = models.CharField(max_length=255)
class Meta:
db_table = "t_settings"
def __str__(self):
return self.id
Form:
class SettingsForm(ModelForm):
class Meta:
model = Settings
fields = ['pkey_path']
View:
def settings_update(request):
if request.method == "POST":
form = SettingsForm(request.POST)
if form.is_valid():
form.save()
return render(request, 't_settings/index.html', {'form': form})
else:
form = SettingsForm()
return render(request, 't_settings/index.html', {'form': form})
urls.py:
app_name = 't_settings'
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^update/', views.settings_update, name='settings-update'),
]
html form:
<form action="{% url 't_settings:settings-update' %}" method="post" class="form-horizontal">
{% csrf_token %}
<div class="box-body">
<div class="form-group">
<label for="{{ form.pkey_path.id_for_label }}" class="col-sm-3 control-label">test</label>
<div class="col-sm-8">{{ form.pkey_path.errors }}
<input type="text" class="form-control"
name="{{ form.pkey_path.name }}"
id="{{ form.pkey_path.id_for_label }}"
placeholder="Path"
value="{{ form.pkey_path.value }}">
I tried different approaches using Django docs, but anyway I get:
{{ form.pkey_path.value }} doesn't show the value from database in template
Form itself works, but adds new rows in database, instead of updating the existing ones
You're not doing anything in your view to either get an existing Settings entry to pass to the form, or tell the form which one to update on save. You need to have some way of identifying the object you want, for a start, which usually means accepting an id or slug in the URL; then you would need to query that object and pass it to the form with the instance parameter, both on initial instantiation and before saving.
You could override the model's save method to check if pkey_path already exists and overwrite existing record if so.
from django.core.exceptions import ObjectDoesNotExist
class Settings(models.Model):
pkey_path = models.CharField(max_length=255)
class Meta:
db_table = "t_settings"
def __str__(self):
return self.id
def save(self, *args, **kwargs):
try:
# will update existing record
self.pk = Settings.objects.get(pkey_path=self.pkey_path).pk
except ObjectDoesNotExist:
# if not existing record, will write a new one
self.pk = None
# call the original save method
super(Settings, self).save(*args,**kwargs)
Note that there will only be an instance associated w/ the form if successfully saved as your view lists now.
If this view is only to update existing records, you could do the following so there is always an instance:
View:
from django.shortcuts import get_object_or_404
from my_app.models import Settings
def settings_update(request, pkey_path=None):
instance = get_object_or_404(Settings,pkey_path=pkey_path)
if request.method == "POST":
form = SettingsForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return render(request, 't_settings/index.html', {'form': form})
else:
form = SettingsForm(instance=instance)
return render(request, 't_settings/index.html', {'form': form})
You must add an entry to urls.py which includes (?P<pkey_path>.+) for this view so that it fetches an existing Settings record. To add a new record, you could write a settings_add view or if no pkey_path supplied to the view make instance=Settings() to instantiate an empty instance of the Settings model.
EDIT #2: Handling a new record vs an existing record requires more than what you have in your view. Here's one way to do that.
In urls.py:
urlpatterns = patterns('',
url(r'^settings/$',views.settings_update, name='add-settings'),
url(r'^settings/(?P<pkey_path>.+)/$',views.settings_update, name='update-settings'),
# ...
)
In views.py:
from django.shortcuts import get_object_or_404
from my_app.models import Settings
def settings_update(request, pkey_path=None):
if pkey_path: # if supplied via url, find record w/ pkey_path. If pkey_path supplied matches no records, return 404.
instance = get_object_or_404(Settings,pkey_path=pkey_path)
else: # if pkey_path not supplied in url, create empty instance
instance = Settings() # instantiate new Settings object
if request.method == "POST": # if post submitted, save if valid
form = SettingsForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return render(request, 't_settings/index.html', {'form': form})
else: # no post submitted
form = SettingsForm(instance=instance)
return render(request, 't_settings/index.html', {'form': form})
I have a view where I need to display information about a certain model instance hence I use a DetailView. I also need that same view to handle a regular form (not a model form), both displaying the form on GET and validating it on POST. To do that, I am trying to use a FormView however the combination of both view clases does not work:
class FooView(FormView, DetailView):
# configs here
In GET (for simplicity of the question I will only show the issue with GET since POST has a different issue), it does not work because the form never gets added to the context. The reason has to do with method resolution order for that class:
>>> inspect.getmro(FooView)
(FooView,
django.views.generic.edit.FormView,
django.views.generic.detail.DetailView,
django.views.generic.detail.SingleObjectTemplateResponseMixin,
django.views.generic.base.TemplateResponseMixin,
django.views.generic.edit.BaseFormView,
django.views.generic.edit.FormMixin,
django.views.generic.detail.BaseDetailView,
django.views.generic.detail.SingleObjectMixin,
django.views.generic.base.ContextMixin,
django.views.generic.edit.ProcessFormView,
django.views.generic.base.View,
object)
Within the request, Django has to get the form and add it to the context. That happens in ProcessFormView.get:
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
However the first class with the MRO which has get defined is BaseDetailView:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
As you can see the BaseDetailView.get never calls super hence the ProcessFormView.get will never be called hence the the form will not be added to the context. This can be fixed by creating a mixin view where all these nuances for GET and POST can be taken care of however I do not feel it is a clean solution.
My question: is there any way of accomplishing what I want with Django's default CBV implementation without creating any mixins?
One solution would be to use mixins, as per limelights' comment above.
Another approach is to have two separate views, one a DetailView and the other a FormView. Then, in the template for the former, display the same form you're using in the latter, except that you won't leave the action attribute empty -- instead, set it to the url for the FormView. Something along the lines of this (please beware of any errors as I'm writing this without any testing):
In views.py:
class MyDetailView(DetailView):
model = MyModel
template_name = 'my_detail_view.html'
def get_context_data(self, **kwargs):
context = super(MyDetailView, self).get_context_data(**kwargs)
context['form'] = MyFormClass
return context
class MyFormView(FormView):
form_class = MyFormClass
success_url = 'go/here/if/all/works'
In my_detail_view.html:
<!-- some representation of the MyModel object -->
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
In urls.py:
# ...
url('^my_model/(?P<pk>\d+)/$', MyDetailView.as_view(), name='my_detail_view_url'),
url('^my_form/$', require_POST(MyFormView.as_view()), name='my_form_view_url'),
# ...
Note that the require_POST decorator is optional, in the case that you don't want the MyFormView to be accessible by GET and want it only to be processed when the form is submitted.
Django also has a rather lengthy documentation about this problem.
https://docs.djangoproject.com/en/1.8/topics/class-based-views/mixins/#using-formmixin-with-detailview
They advise to make 2 different views, and have the detail view refer to the form view on post.
I'm currently seeing if this hack might work:
class MyDetailFormView(FormView, DetailView):
model = MyModel
form_class = MyFormClass
template_name = 'my_template.html'
def get_context_data(self, **kwargs):
context = super(MyDetailFormView, self).get_context_data(**kwargs)
context['form'] = self.get_form()
return context
def post(self, request, *args, **kwargs):
return FormView.post(self, request, *args, **kwargs)
By using FormMixin
views.py
from django.contrib.auth import get_user_model
from django.core.urlresolvers import (
reverse_lazy
)
from django.http import Http404
from django.shortcuts import (
render,
redirect
)
from django.views.generic import (
DetailView,
FormView,
)
from django.views.generic.edit import FormMixin
from .forms import SendRequestForm
User = get_user_model()
class ViewProfile(FormMixin, DetailView):
model = User
context_object_name = 'profile'
template_name = 'htmls/view_profile.html'
form_class = SendRequestForm
def get_success_url(self):
return reverse_lazy('view-profile', kwargs={'pk': self.object.pk})
def get_object(self):
try:
my_object = User.objects.get(id=self.kwargs.get('pk'))
return my_object
except self.model.DoesNotExist:
raise Http404("No MyModel matches the given query.")
def get_context_data(self, *args, **kwargs):
context = super(ViewProfile, self).get_context_data(*args, **kwargs)
profile = self.get_object()
# form
context['form'] = self.get_form()
context['profile'] = profile
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
#put logic here
return super(ViewProfile, self).form_valid(form)
def form_invalid(self, form):
#put logic here
return super(ViewProfile, self).form_invalid(form)
forms.py
from django import forms
class SendRequestForm(forms.Form):
request_type = forms.CharField()
def clean_request_type(self):
request_type = self.cleaned_data.get('request_type')
if 'something' not in request_type:
raise forms.ValidationError('Something must be in request_type field.')
return request_type
urls.py
urlpatterns = [
url(r'^view-profile/(?P<pk>\d+)', ViewProfile.as_view(), name='view-profile'),
]
template
username -{{object.username}}
id -{{object.id}}
<form action="{% url 'view-profile' object.id %}" method="POST">
{% csrf_token %}
{{form}}
<input type="submit" value="Send request">
</form>
In Django By Example from lightbird, they're using a library, MCBV, to mix generic views:
My guide’s tutorials will use a library of class-based views based on modified Django generic views; the library is called MCBV (M stands for modular) and the main difference compared to generic CBVs is that it’s possible to mix and match multiple generic views easily (e.g. ListView and CreateView, DetailView and UpdateView, etc.)
You can follow the explanation here: helper-functions
And use it to mix FormView and DetailView, or whatever
Code: MCBV
I performed my solution using ModelForms and something like this:
On method get_context_data of my DetailView I made:
form = CommentForm(
instance=Comment(
school=self.object, user=self.request.user.profile
)
)
context['form'] = form
And my FormView was like:
class SchoolComment(FormView):
form_class = CommentForm
def get_success_url(self):
return resolve_url('schools:school-profile', self.kwargs.get('pk'))
def form_valid(self, form):
form.save()
return super(SchoolComment, self).form_valid(form)
That's a old post but good for reference.
One elegant and reusable away is to use a custom inclusion tag for the form.
templatetags/my_forms_tag.py
from django import template
from ..forms import MyFormClass
register = template.Library()
#register.inclusion_tag('<app>\my_form.html')
def form_tag(*args, **kwargs):
my_form = MyFormClass()
return {'my_form ':my_form}
my_form.html
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
The post will be taken by FormView werever view you put inclusion tag. And it can receive any context you pass in the inclusion. Dont forget to load my_form_tag and create the view for MyForm and include the entry in urls.py