My online marketing department tracks thousands of rows of email and analytic marketing data and I am building a tool that we can use in-house to manage the data so we can run multiple queries to produce reports.
The CRUD operations are working normally, but with so much data to enter, its inefficient to have ticket_form.html, ticket_list.html and a ticket_confirm_delete.html views and templates. My list data is in table format, so what I would like to do is combine the operations under a single view so I can add a column at the end of my table with icons to add, update and delete the row. Thank you for your help.
My current views, I'd like to combine:
class TrafficForm(ModelForm):
class Meta:
model = Traffic
fields = ['sessions', 'new_users', 'reminder', 'campaigns', 'new_sales', 'sales_renewals']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['sessions'].widget.attrs.update({
'placeholder': 'Sessions',
...
})
for field in iter(self.fields):
self.fields[field].widget.attrs.update({
'class': 'form-control placeholder-no-fix'
})
def traffic_create(request, template_name='traffic_form.html'):
form = TrafficForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('traffic_list')
return render(request, template_name, {'form': form})
def traffic_list(request, template_name='traffic_list.html'):
traffic = Traffic.objects.all()
data = {}
data['object_list'] = traffic
return render(request, template_name, data)
def traffic_update(request, pk, template_name='traffic_form.html'):
traffic = get_object_or_404(Traffic, pk=pk)
form = TrafficForm(request.POST or None, instance=traffic)
if form.is_valid():
form.save()
return redirect('traffic_list')
return render(request, template_name, {'form': form})
def traffic_delete(request, pk, template_name='traffic_confirm_delete.html'):
traffic = get_object_or_404(Traffic, pk=pk)
if request.method == 'POST':
traffic.delete()
return redirect('traffic_list')
return render(request, template_name, {'object': traffic})
Use the following code for reference
class TrafficForm(ModelForm):
def get_absolute_url(self):
#return edit url
def get_delete_url(self):
#return delete url
View function
def crud_traffic_view(request, template_name= "crud_traffic.html"):
all_objects = Traffic.objects.all()
form = TrafficForm()
context_dict = {"form" : form, "all_objects" : all_objects}
return render(request, template_name, {'all_objects' : all_objects})
Template
<table>
{% for traffic in all_objects %}
<tr>
<td>{{traffic.details}} </td>
<td>Edit</td>
<td> Delete</td>
</tr>
{% endfor %}
</table>
<form action="{% url 'add_traffic' %}" method="POST">
{% csrf %} {{form}}
...
</form>
This would let you manage all things from one page (only going to another page for edit/delete confirmation).
You can add ajax calls to update the template if you wish to make it even faster.
Related
I want to render a view with some content. I don't get why the context isn't rendered.
The render_recall_details() function causes problems --> ctx is not None, In my opinion there is no reason why it is not rendered in the html file
views.py
class RecallDetail(View):
template_name = "recall_detail.html"
def get(self, request, *args, **kwargs):
if request.GET:
q = request.GET
q = q.dict()
recall = find_recall_by_querystring(q)
if recall:
self.render_recall_details(request, recall)
else:
return render(request, self.template_name)
return render(request, self.template_name)
def render_recall_details(self, request, obj, *args, **kwargs):
ctx = {
'head': 'Hallo',
'rec': RecallForm(),
'docs': find_docs(obj),
}
print(ctx)
return render(request, self.template_name, context=ctx)
forms.py
class RecallForm(forms.ModelForm):
class Meta:
model = Recall
fields = ('Recall_CODE', 'Recall_NAME', 'Recall_DESCRIPTION', 'Recall_START_DATE', 'Recall_PLANNED_COMPLETATION_DATE', 'Recall_STATUS', 'Recall_DATE_COMPLETED')
my html_template:
{% extends 'base.html' %}
{% block content %}
<div class="content-wrapper">
<h1>{{ head }}</h1>
...
</div>
{% endblock content %}
It seems like a typo in your code-base. In Django, every view must return a response. So, in your case, you are not returning anything if the variable recall has some value. So, change your view as,
class RecallDetail(View):
template_name = "recall_detail.html"
def get(self, request, *args, **kwargs):
if request.GET:
q = request.GET
q = q.dict()
recall = find_recall_by_querystring(q)
if recall:
# you need to put the `return` statement here
return self.render_recall_details(request, recall)
else:
return render(request, self.template_name)
return render(request, self.template_name)
def render_recall_details(self, request, obj, *args, **kwargs):
ctx = {
'head': 'Hallo',
'rec': RecallForm(),
'docs': find_docs(obj),
}
print(ctx)
return render(request, self.template_name, context=ctx)
I am trying to create a form to submit a blog post on an author detail page, so that the blog post will automatically use the current author as its "blog_author" foreign key. I'm aware that this approach isn't "secure" - it's a project site, and I'm trying to learn a new design pattern.
The Django docs recommended using 1 parent view and 2 subviews to handle get and post respectively (https://docs.djangoproject.com/en/3.0/topics/class-based-views/mixins/).
The page renders fine with the get, but the post gives me an error reading "Page not found (404) - no blog post found matching the query." The exception is raised by my parent view (blog.views.AuthorDetail), but there is no traceback.
Edit: Form should have been a ModelForm from the beginning
Here are my views:
class BlogAuthorDetailView(generic.DetailView):
model = BlogAuthor
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = BlogSubmitForm()
return context
class BlogSubmit(SingleObjectMixin, FormView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogPost
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
#Should I be overriding form_valid() to use the line above? Not sure if I'm doing my data
#handling in the right place
return super().post(request, *args, **kwargs)
def form_valid(self, form):
blogpost = form.save(commit=False)
blogpost.blog_author = self.object
blogpost.save()
return redirect('blog_author-detail', pk=self.object.id)
class AuthorDetail(View):
def get(self, request, *args, **kwargs):
view = BlogAuthorDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = BlogSubmit.as_view()
return view(request, *args, **kwargs)
URLs:
urlpatterns = [
path('', views.index, name='index'),
path('blogs/', views.BlogPostListView.as_view(), name='blogs'),
path('blog/<int:pk>', views.BlogPostDetailView.as_view(), name='blogpost-detail'),
path('bloggers/', views.BlogAuthorListView.as_view(), name='bloggers'),
path('blogger/<int:pk>', views.AuthorDetail.as_view(), name='blog_author-detail'),
path('blog/<int:pk>/create', views.BlogCommentCreate.as_view(), name='comment_create')
]
the template:
{% extends "base_generic.html" %}
{% block content %}
<h1>Title: {{ blogauthor.title }}</h1>
<p><strong>Author:</strong> {{ blogauthor }}</p>
<p><strong>Biography:</strong> {{ blogauthor.biography }}</p>
<p><strong>User:</strong> {{ blogauthor.user }}</p>
<p><strong>Posts:</strong>
{% for blog in blogauthor.blogpost_set.all %}
<p> {{ blog.title }} </p>
{% endfor %} </p>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
<div style="margin-left:20px;margin-top:20px">
<h4>Comments: Coming Soon!</h4>
{% endblock %}
Model:
class BlogPost(models.Model):
date_created = models.DateField(blank=False, default = date.today)
blog_author = models.ForeignKey('BlogAuthor', on_delete = models.SET_NULL, null=True)
title = models.TextField(max_length=70)
content = models.TextField(max_length=400, null=False)
class Meta:
ordering = ['date_created']
def get_absolute_url(self):
"""Returns the url to access a particular blog post instance."""
return reverse('blogpost-detail', args=[str(self.id)])
def __str__(self):
return self.title
And the forms.py:
class BlogSubmitForm(forms.Form):
title = forms.CharField()
content = forms.CharField(widget=forms.Textarea(attrs={'cols': 40, 'rows': 8}))
date_created = forms.DateField()
At this point, I suspect that the problem is related to my redirect() call in the form_valid override.
The things I have tried include:
Changing the form’s action from blank to the same URL as in my URL paths (possible I did this wrong)
Changing the code in form_valid() to read form.instance.blog_author = self.object (same exact error message, so I don’t think it’s this)
Fiddling with the form_valid()’s redirect call, including: using self.object instead or a URL, using a hardcoded url, getting rid of the second argument, and changing the 2nd arg to pk=, slug=.
Adding a get_success_url override (don’t really know why this would work)
edit: one of the excepted post calls that showed up in my local server went to blog/blogger/4, which is the url I want. Not sure what the issue is.
This is confusing on how you are using the template. Anyway, I think the simplest solution here is to get the BlogAuthor data from request.user and that is most logical, otherwise, anyone can post anything from another user as long as they can predict their primary key(which is a security hole). Here is how you can try:
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogSubmit(LoginRequiredMixin, CreateView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogPost
def get_success_url(self):
return reverse('blog_author-detail', pk=self.object.id)
def form_valid(self, form):
form.blog_author = self.request.user.blogauthor # assuming BlogAuthor has OneToOne relation with User
return super(BlogSubmit, self).form_valid(form)
Update
Purpose of FormView is to collect data from Forms, where CreateView is to store and create a new instance. Anyway, you need to change your code like this to make it work:
class BlogSubmit(LoginRequiredMixin, SingleObjectMixin, FormView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogAuthor
def get_success_url(self):
return reverse('blog_author-detail', pk=self.object.id)
def form_valid(self, form):
self.object = self.get_object()
form.blog_author = self.object
form.save()
return super(BlogSubmit, self).form_valid(form)
Also update the form:
class BlogSubmitForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['title', 'date_created', 'content']
FYI, to make SingleObjectMixin work, you need to change the model from BlogPost to BlogAuthor
I am unable to see pre-existing form data when updating. The forms work fine, after submitting the database is updated, but in order to submit the user must enter all form data (including data that will not be updated). While reentering, the previous data is not visible. Is there a way to display the current data of the model instance being updated in the form fields?
Forms:
UpdateSomethingForm(forms.ModelForm):
class Meta:
model = Something
fields = ['field1', 'field2', 'field3']
Views:
def update_something(request, object_pk):
form = UpdateSomethingForm()
context_dict = {}
try:
instance = Something.objects.get(pk=object_pk)
context_dict['instance'] = instance
except Something.DoesNotExist:
context_dict['instance'] = None
if request.method == 'POST':
form = UpdateSomethingForm(request.POST, instance=instance)
if form.is_valid():
form.save(commit=True)
return HttpResponseRedirect('/home')
else:
print(form.errors)
context_dict['form'] = form
return render(request, 'form.html', context=context_dict)
Html:
<form role="form" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
You passed the instance argument on POST, but not on GET.
form = UpdateSomethingForm(instance=instance)
in full:
def update_something(request, object_pk):
try:
instance = Something.objects.get(pk=object_pk)
except Something.DoesNotExist:
instance = None
if request.method == 'POST':
form = UpdateSomethingForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return HttpResponseRedirect('/home')
else:
form = UpdateSomethingForm(instance=instance)
context_dict = {'form': form, 'instance': instance}
return render(request, 'form.html', context_dict)
The main problem is that you construct an empty Form, even if the instance can be found. But you make the view rather "chaotic" in the first place.
Probably a more readable view is:
def update_something(request, object_pk):
context_dict = {}
try:
instance = Something.objects.get(pk=object_pk)
except Something.DoesNotExist:
instance = None
context_dict['instance'] = instance
if request.method == 'POST':
form = UpdateSomethingForm(request.POST, instance=instance)
if form.is_valid():
form.save(commit=True)
return redirect('view_name')
else:
form = UpdateSomethingForm(instance=instance)
context_dict['form'] = form
return render(request, 'form.html', context=context_dict)
Here we ensure that the instance variable is always defined, also in the case the except body is "firing".
Furthermore it is probably better to use a redirect(..) and pass the name of the view over an URL, since if you change the URL of that view, this will still work.
I have two forms in template. At the moment I have two submit buttons.
Would like to combine those to a single submit button.
Below code is now updating only one form, that's AnswerForm.
How i can update AnswerReplyForm along with that?
class AnswerView(ObjectEditView):
form_class = forms.AnswerReplyForm
answer_form = forms.AnswerForm
model = AnswerReply
def get(self, request, pk):
answer = get_object_or_404(Answer, pk = pk)
answer_reply = AnswerReply.objects.filter(answer_id = pk).order_by('-id')
self.answer_form = self.answer_form(instance=answer)
return render(request, 'helpdesk/answer.html', {
'answer': answer,
"answer_reply" : answer_reply,
'obj_type': 'answer reply',
'form': self.form_class,
"form2":self.answer_form,
"pre_reply_from" : self.predefined_reply_form
})
def post(self, request, pk, *args, **kwargs):
answer = get_object_or_404(Answer, id=pk)
answer_reply = AnswerReply.objects.filter(answer_id = pk).order_by('-id')
self.answer_form = self.answer_form(instance=answer)
obj = self.model()
obj = self.alter_obj(obj, request, args, kwargs)
form = self.form_class(request.POST, request.FILES, instance=obj)
if form.is_valid():
form.instance.answer_id = pk
obj_created = not form.instance.pk
obj = form.save()
return render(request, 'helpdesk/answer.html', {
'answer': answer,
"answer_reply" : answer_reply,
'obj_type': 'answer reply',
'form': self.form_class,
"form2":self.answer_form,
})
In general:
if request.method == 'POST':
form_1 = FormOne(request.POST)
form_2 = FormTwo(request.POST)
if form_1.is_valid() and form_2.is_valid():
form_1.save()
form_2.save()
return #Write your return here, something like HttpResposeRedirect or whatever you need to do after saving both form successfully
else:
form_1 = FormOne()
form_2 = FormTwo()
context = {
'form1': form_1,
'form2': form_2
}
return render(request, 'template.html', context)
In your template file
<form>
{{ form1 }}
{{ form2 }}
<input type="submit" value= "submit">
</form>
It will work.
It's better to define a structure for each one (View, route and template)
Then, based on desired condition, display one of the structures (redirect to one of them):
for example decision view:
def decisionView(request):
route = '/route/1'
if condition:
route = '/route/2'
return redirect(route)
i hope this could help you
I'm trying to put multiple account management forms on the one page with a TemplateView as follows:
class AccountManagement(TemplateView):
""" Generic view to display the account management template """
template_name = 'accountmanagement.html'
def get_context_data(self, **kwargs):
context = super(AccountManagement, self).get_context_data(**kwargs)
context['user'] = self.request.user
# pass unbound form instances to context
# if there aren't bound instances already there
if context.get('usercreate_form') is None:
context['usercreate_form'] = UserCreateForm()
if context.get('password_form') is None:
context['password_form'] = PasswordChangeForm()
return context
I'm handling UserCreation with a FormView (because this example is simplified; I also need some non-model data, and CreateView needs a ModelForm). This view processes the POST request, and is supposed to redirect to the TemplateView with a success message, or pass the invalid bound form back to the context so that the template can render the errors. Trouble is, it doesn't do the part in bold italics (obviously HttpResponseRedirect doesn't pass the context). Why? How can I get the bound form back into the TemplateView context here so that the form.errors will be available and the user doesn't have to retype the data?
class UserCreate(FormView):
"""
Generic view to create a User.
"""
form_class = UserCreateForm
http_method_names = ['post',]
success_url = reverse_lazy('accountmanagement')
failure_url = reverse_lazy('accountmanagement')
def form_valid(self, form):
#password1 == password2 as per UserCreateForm.clean()
try:
new_user = User.objects.create_user(
username=form.cleaned_data['email'],
first_name=form.cleaned_data['first_name'],
last_name=form.cleaned_data['last_name'],
email=form.cleaned_data['email'],
password=form.cleaned_data['password1']
)
new_user.save()
messages.success(self.request, new_user.username + str(_(": successfully saved.") ))
return HttpResponseRedirect(self.success_url)
except IntegrityError:
#duplicate username
messages.error(self.request, _("Duplicate email address."))
return HttpResponseRedirect(self.failure_url, {'usercreate_form': form})
def form_invalid(self, form):
messages.error(self.request, _("Unable to create user."))
return HttpResponseRedirect(self.failure_url, {'usercreate_form': form})
template:
<form method="post" action="{% url 'usercreate' %}">{% csrf_token %}
{{ usercreate_form.errors }}
{{ usercreate_form.as_p }}
<button type="submit">{% trans 'Save' %}</button>
Final related question: is the right way to put several related forms on the one page a TemplateView? And then process non-model forms using a POST-only FormView with a redirect back to the TemplateView? Or should I do this a different way?