Inject non-form element into dynamic Django form? - django

Is it possible to inject a non-form element into a dynamic Django form's context? I have a "Delete user photos" form that I want to contain a thumbnail image of each user with a BooleanField checkbox and label right below it:
+------------+
| |
| photo |
| |
+------------+
[x] Delete <username>'s photos
Right now I know how to create the dynamic checkboxes and their labels but I'm not sure how to go about adding each user's photo. As can be seen from my code below, the name attribute of each HTML input tag will contain the user's ID and I'll examine this attribute when the user submits the form to determine whether to delete their photos or not. I'd like to insert an tag just above each input tag that links to the user's profile photo. The image tag's "src" attribute will contain the user's ID which creates the link to their photo. Is there a way to "inject" this non-form image tag into the context of this dynamic form in order to render an image tag just above each checkbox input tag?
Thanks.
# views.py
def remove_access_to_private_photos(request, template):
if request.method == 'POST':
form = RemovePrivatePhotoAccessForm(request.POST, this_user_id=request.user.id)
if form.is_valid():
for name, value in form.cleaned_data.items():
if value == True:
# Profile links to User via a OneToOneField
this_user = Profile.objects.get(user_id=request.user.id)
other_user = Profile.objects.get(user_id=name)
this_user.remove_private_access(other_user_prof)
return redirect('photos-home')
else:
form = RemovePrivatePhotoAccessForm(this_user_id=request.user.id)
context = {'form': form}
return render(request, template, context)
# models.py
class RemovePrivatePhotoAccessForm(forms.Form):
def __init__(self, *args, **kwargs):
this_user_id = kwargs.pop('this_user_id')
super(RemovePrivatePhotoAccessForm, self).__init__(*args, **kwargs)
user = User.objects.get(pk=this_user_id)
user_prof = Profile.objects.get(user=user)
other_user_id_list = user_prof.gave_private_access().values_list('user_id', flat=True)
for id in other_user_id_list:
other_user = User.objects.get(pk=id)
self.fields[str(id)] = forms.BooleanField(required=False)
self.fields[str(id)].label = mark_safe('%s') % (id, this_user_id, other_user.username)
# delete_photos.html
<form action="." method="post">
{% csrf_token %}
{% for field in form %}
{# I'D LIKE TO PUT <IMG> TAG HERE #}
{{ field }} Delete {{ field.label|safe }}'s photos
{% endfor %}
<input type="submit" value="Submit" />
</form>

A form field is just a class, so you can add whatever properties you need when or after you instantiate it. Your view remains unchanged given this example code.
# forms.py
from .models import Profile # or whatever
class RemovePrivatePhotoAccessForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user_id')
user_profile = Profile.objects.get(pk=user_id)
other_profiles = user_profile.gave_private_access()
for profile in other_profiles:
id = str(profile.id)
field = forms.BooleanField(required=False)
field.label = mark_safe('%s') % (id, user_profile.id, profile.username)
field.photo = profile.photo
self.fields[id] = field
super(RemovePrivatePhotoAccessForm, self).__init__(*args, **kwargs)
# delete_photos.html
<form action="." method="post">
{% csrf_token %}
{% for field in form %}
<img src="{{ field.photo.url }}" />
{{ field }} Delete {{ field.label|safe }}'s photos
{% endfor %}
<input type="submit" value="Submit" />
</form>

Here's how I solved this problem. First I created a custom template tag:
# photos/templatetags/photos_extras.py
from django import template
from django.contrib.auth.models import User
from django.utils.safestring import mark_safe
register = template.Library()
#register.simple_tag
def render_image(uid):
user = User.objects.get(pk=uid)
src_string = ''.join([
'/photos/',
user.username, '_',
user.profile.image_id,
'_thumb.jpg'])
img_tag = ''.join([
'<img src="',
src_string,
'" alt="',
user.username,
'" />'])
return mark_safe(img_tag)
I then inserted the custom template tag into my template. field.name contains the desired user's user ID, and render_image returns the desired HTML img tag.
# delete_photos.html
<form action="." method="post">
{% csrf_token %}
{% for field in form %}
{% render_image field.name %}
{{ field }} Delete {{ field.label|safe }}'s photos
{% endfor %}
<input type="submit" value="Submit" />
</form>

Related

I can't post in Django

I can't post in Django, because when I import an image it doesn't work for me. it tells me that there's no file selected but I selected one.
This is the post model that I created, models.py file:
class Post(models.Model):
publisher = models.ForeignKey(User,on_delete=models.CASCADE)
caption = models.CharField(max_length=100)
date_created = models.DateTimeField(default=timezone.now())
image = models.ImageField(upload_to="post_images")
def __str__(self):
return self.caption
here's the forms.py file for the Post model:
from django import forms
from .models import Post
class CreatePostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['caption','image']
here's the Publish function in views.py file which implements the logic for my publish feature:
#login_required
def Publish(request):
if request.method == "POST":
form = CreatePostForm(request.POST,request.FILES)
if form.is_valid():
form.publisher = request.user
form.save()
return redirect("home")
else:
form = CreatePostForm()
return render(request,"posts/publish.html",{
"form":form,
})
int the urls.py file:
from django.urls import path
from . import views
urlpatterns = [
path('publish/',views.Publish,name="publish"),
path('',views.home,name="home"),
]
and here's in html template:
{% extends "users/base.html" %}
{% load crispy_forms_tags %}
{% block title %}create{% endblock title%}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-sm-6 col-md-5 authentification">
<div class="form-header">
<h1>
publish
</h1>
</div>
<div class="form-body">
<form method="POST">
<fieldset class="form-group" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-primary form-control">publish</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}
the Django version used is 2.2 and the Python 3.8. and Windows 10 Pro
You should alter the .publisher attribute of the .instance wrapped in the form, not the form itself, so:
#login_required
def Publish(request):
if request.method == 'POST':
form = CreatePostForm(request.POST,request.FILES)
if form.is_valid():
form.instance.publisher = request.user
form.save()
return redirect('home')
else:
form = CreatePostForm()
return render(request,'posts/publish.html',{
'form': form,
})
Since you are submitting both files and data, you should specify the enctype=… attribute [mdn] in the <form>:
<form enctype="multipart/form-data" method="POST">
…
</form>
Note: Django's DateTimeField [Django-doc]
has a auto_now_add=… parameter [Django-doc]
to work with timestamps. This will automatically assign the current datetime
when creating the object, and mark it as non-editable (editable=False), such
that it does not appear in ModelForms by default.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Edit Model data using ModelForm: ModelForm validation error

I am working on my first django app. I am building an app that allows the user to rate beer. I want my user to be able to edit an entry they've already created. I take them to a ModelForm, and ask for their entry. When the POST method is called, my data is invalid. Here is my model.py:
from django.db import models
class Rating(models.Model):
beer_name = models.TextField()
score = models.DecimalField(max_digits=2, decimal_places=1)
notes = models.TextField(blank=True)
brewer = models.TextField(blank=True)
and forms.py:
from django import forms
from ratings.models import Rating
class RatingForm(forms.ModelForm):
class Meta:
model = Rating
fields = ['beer_name', 'score', 'notes', 'brewer']
Here is the views.py of my edit function:
def edit(request, row_id):
rating = get_object_or_404(Rating, pk=row_id)
if request.method == "POST":
form = RatingForm(request.POST, instance=rating)
if form.is_valid():
form.save()
return redirect(home)
else:
return HttpResponse("Invalid entry.")
else:
context = {'form': rating}
form = RatingForm(instance=rating)
return render(
request,
'ratings/entry_def.html',
context
)
However, every time the POST is called I get an "Invalid entry." HttpResponse, meaning my form.is_valid() is being returned False. Here is my template:
{% extends "base.html" %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-sm-10 col-sm-offset-1">
<h2>Edit Rating</h2>
<form role="form" method="post">
{% csrf_token %}
<p>Beer Name: <textarea>{{ form.beer_name }}</textarea></p>
<p>Score: <input type="text" name="BeerScore" value="{{ form.score }}"></p>
<p>Notes: <textarea>{{ form.notes }}</textarea></p>
<p>Brewer: <textarea>{{ form.brewer }}</textarea></p>
<p><button type="submit" class="save btn btn-primary">Save</button></p>
<p><button type="reset" class="btn btn-primary">Cancel</button></p>
</form>
</div>
</div>
</div>
{% endblock %}
So when I press my Save button, I am getting the response. Here is my edit url in urls.py:
urlpatterns = [
...
url(r'rating/edit/(?P<row_id>[0-9]+)/$', edit , name='rating-edit'),
]
You're wrapping fields in other fields which don't have name attributes. This is most likely causing the values to be excluded from the request.POST data.
Additionally, Django form fields all have a corresponding HTML widget. So there's really no need to render the HTML by hand, unless you need to.
Change your template code to:
<p>
{{ form.beer_name.label }}: {{ form.beer_name }}
{% if form.beer_name.errors %}
<br />{{ form.beer_name.errors }}
{% endif %}{# repeat for other fields as needed #}
</p>
<p>{{ form.score.label }}: {{ form.score }}</p>
<p>{{ form.notes.label }}: {{ form.notes }}</p>
<p>{{ form.brewer.label }}: {{ form.brewer }}</p>
<p><button type="submit" class="save btn btn-primary">Save</button></p>
<p><button type="reset" class="btn btn-primary">Cancel</button></p>
If you need to change the widget, do so at the form class level:
class RatingForm(forms.ModelForm):
class Meta:
model = Rating
def __init__(self, *args, **kwargs):
super(RatingForm, self).__init__(*args, **kwargs)
self.fields['notes'].widget = forms.Textarea()
This way, Django manages the attributes and binding for you.
Your view can also use some cleanup:
def edit(request, row_id):
rating = get_object_or_404(Rating, pk=row_id)
form = RatingForm(request.POST or None, instance=rating)
if request.method == "POST" and form.is_valid():
form.save()
return redirect(home)
context = {'form': rating}
return render(request, 'ratings/entry_def.html', context)

How can I ask for user input with an action in django admin?

In my code, I am writing an action for grouping, I would like to ask the user how many people would they like per group and then respond with an alert box that says something along the lines of you have 4 groups, based on user input. How do I do this in django admin, how do I create some kind of pop up that asks for the amount of people that they would like to put in a group? (I'm trying to achieve this with an action)
admin.py:
Def howmany (modeladmin, request, queryset):
people = queryset.count()
amount_per = [the number that the user inputs]
Amount_of_groups = people/amount_per
A more simplified and better approach I found here:
You just need to create an Action Form like this.
from django.contrib.admin.helpers import ActionForm
from django import forms
class XForm(ActionForm):
x_field = forms.ModelChoiceField(queryset=Status.objects.all(), required=False)
Now, define that XForm in your admin.py
class ConsignmentAdmin(admin.ModelAdmin):
action_form = XForm
actions = ['change_status']
def change_status(modeladmin, request, queryset):
print(request.POST['x_field'])
for obj in queryset:
print(obj)
change_status.short_description = "Change status according to the field"
admin.py Something like:
class MyAdmin(admin.ModelAdmin):
def howmany(modeladmin, request, queryset):
people = queryset.count()
amount_per = [the number that the user inputs]
Amount_of_groups = people/amount_per
if 'apply' in request.POST:
form = AmountPerForm(request.POST)
if form.is_valid():
amount_per = form.cleaned_data['amount_per']
self.message_user(request, u'You selected - %s' % amount_per)
return HttpResponseRedirect(request.get_full_path())
else:
form = AmountPerForm()
return render(request, 'admin/amount_per_form.html', {
'items': queryset.order_by('pk'),
'form': form,
'title': u'Your title'
})
File "admin/amount_per_form.html" contains something like:
{% extends 'admin/base_site.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="action" value="howmany" />
{% for item in items %}
<input type="hidden" name="_selected_action" value="{{ item.pk }}"/> {# thanks to #David Hopkins for highligting it #}
{% endfor %}
{{ form }}
<p>Apply for:</p>
<ul>{{ items|unordered_list }}</ul>
<input type="submit" name="apply" value="Apply" />
</form>
{% endblock %}

form wizard initial data for edit not loading properly in Django?

I have a three page form-list coming out of a single model. I could save the model first time, but when I want to edit the model, only the first form shows the initial value, subsequent forms does not show the initial data. but when I print the initial_dict from views, I can see all the initial views correctly. I followed this blog on form wizard.
Here is my model.py:
class Item(models.Model):
user=models.ForeignKey(User)
price=models.DecimalField(max_digits=8,decimal_places=2)
image=models.ImageField(upload_to="assets/", blank=True)
description=models.TextField(blank=True)
def __unicode__(self):
return '%s-%s' %(self.user.username, self.price)
urls.py:
urlpatterns = patterns('',
url(r'^create/$', MyWizard.as_view([FirstForm, SecondForm, ThirdForm]), name='wizards'),
url(r'^edit/(?P<id>\d+)/$', 'formwizard.views.edit_wizard', name='edit_wizard'),
)
forms.py:
class FirstForm(forms.Form):
id = forms.IntegerField(widget=forms.HiddenInput, required=False)
price = forms.DecimalField(max_digits=8, decimal_places=2)
#add all the fields that you want to include in the form
class SecondForm(forms.Form):
image = forms.ImageField(required=False)
class ThirdForm(forms.Form):
description = forms.CharField(widget=forms.Textarea)
views.py:
class MyWizard(SessionWizardView):
template_name = "wizard_form.html"
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT))
#if you are uploading files you need to set FileSystemStorage
def done(self, form_list, **kwargs):
for form in form_list:
print form.initial
if not self.request.user.is_authenticated():
raise Http404
id = form_list[0].cleaned_data['id']
try:
item = Item.objects.get(pk=id)
###################### SAVING ITEM #######################
item.save()
print item
instance = item
except:
item = None
instance = None
if item and item.user != self.request.user:
print "about to raise 404"
raise Http404
if not item:
instance = Item()
for form in form_list:
for field, value in form.cleaned_data.iteritems():
setattr(instance, field, value)
instance.user = self.request.user
instance.save()
return render_to_response('wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list], })
def edit_wizard(request, id):
#get the object
item = get_object_or_404(Item, pk=id)
#make sure the item belongs to the user
if item.user != request.user:
raise HttpResponseForbidden()
else:
#get the initial data to include in the form
initial = {'0': {'id': item.id,
'price': item.price,
#make sure you list every field from your form definition here to include it later in the initial_dict
},
'1': {'image': item.image,
},
'2': {'description': item.description,
},
}
print initial
form = MyWizard.as_view([FirstForm, SecondForm, ThirdForm], initial_dict=initial)
return form(context=RequestContext(request), request=request)
template:
<html>
<body>
<h2>Contact Us</h2>
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
{% for field in form %}
{{field.error}}
{% endfor %}
<form action={% url 'wizards' %} method="post" enctype="multipart/form-data">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">"first step"</button>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">"prev step"</button>
{% endif %}
<input type="submit" value="Submit" />
</form>
</body>
</html>
EDIT:
one this I noticed is the following:
On the edit mode, i.e, when I am at the following url : http://127.0.0.1:8000/wizard/edit/1/,
it displays the first form data correctly, and when I click submit, it is not taking me to step-2 of edit mode, i.e the URL changes to http://127.0.0.1:8000/wizard/create/.
If upon clicking submit on edit url (like /wizard/edit/1) in the first step, same url is maintained then the form would get its initial data in next step. but I cannot figure out how to avoid the url from changing to /wizard/create
The error looks trivial. In your template the form action has wizards url, which is url of create view. Hence when the form is submitted it goes to /wizard/create.
To able to use the template for both views, you can remove the action attribute from form tag. The form will be submitted to current url which can be create or edit.
So change your template to have form tag as
<form method="post" enctype="multipart/form-data">
EDIT: To save item
Update your view as:
def done(self, form_list, **kwargs):
for form in form_list:
print form.initial
if not self.request.user.is_authenticated():
raise Http404
id = form_list[0].cleaned_data['id']
try:
item = Item.objects.get(pk=id)
print item
instance = item
except:
item = None
instance = None
if item and item.user != self.request.user:
print "about to raise 404"
raise Http404
if not item:
instance = Item()
#moved for out of if
for form in form_list:
for field, value in form.cleaned_data.iteritems():
setattr(instance, field, value)
instance.user = self.request.user
instance.save()
return render_to_response('wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list], })

Updating form in Django. How to design?

I'm a django newbie.
I have this model:
class Item(models.Model):
name = models.CharField(max_length=255)
quantity = models.IntegerField()
How to create view to update quantity of my all Item?
views:
def item_list(request):
item = Product.objects.all()[:6]
return render_to_response('item.html',{'item':item},context_instance=RequestContext(request))
form:
from django import forms
class QuantityForm(forms.Form):
quan = forms.IntegerField()
template:
{% for i in item %}
{{ i.name }}
{{ i.quantity }}
{% endfor %}
I'm trying to do something like this(after clicking "update" value quantity in my model should be actualize):
Please any help. Thanks
First you need a view, which retrieves item id and quantity value, updates relative Item instance and redirects you back to the page. Here is an example:
from django.views.decorators.http import require_http_methods
from django.shortcuts import redirect, get_object_or_404
#require_http_methods(["POST"])
def update_item(request)
id = request.POST.get('id', None) #retrieve id
quantity = request.POST.get('q', None) #retrieve quantity
item = get_object_or_404(Item, id=id) #if no item found raise page not found
if quantity:
#updating item quantity
item.quantity = quantity
item.save()
return redirect('my-item-list-view-name')
Also you need to create urlpattern for the view in your urls.py. For example:
...
url(r'^update-item/$', 'update_item', name='update_item'),
...
Then you can make a forms for each item on a template:
{% for i in item %}
<form action="{% url 'update_item' %}" method="post">
{% csrf_token %}
{{ i.name }}
<input name="id" type="hidden" value="{{ i.id }}" />
<input name="q" type="text" value="{{ i.quantity }}" />
<input type="submit" value="update" />
</form>
{% endfor %}
I'm trying to offer a solution as simple as possible. You should know that django provides a lot of cool stuff, that can help your to solve your problem much efficiently, such as forms, modelforms, formsets...
You can either create a ModelForm for your item and then use Formsets or if you can use jquery to submit a ajax request to a django view that updates the item for the selected model
$('<yourbuttonclass').onClick(function(e){
e.preventdefault()
$.post(urlToPost, dataFromTextField&csrfmiddlewaretken={{csrf_token}})
.success('Successfully Updated')
....
In your view:
#Get the item id from urlconf
#ajax_request
def update_view(request, item_id)
#Update your item
return {'Success': 'Updated to blah'}
I like to use the ajax_request decorator from here for sending ajax response. You can also send a HTTPResponse('Successfully updated')
If you want to creating a Restful resource would be a great way too and then you get an interface to create, update, read and delete from a single pattern. Read up on Django Tastypie
in views.py :
if request.method=='POST':
if 'txt1' in request.POST:
if request.POST['txt1']!='':
obj=Item.objects.get(pk=request.POST['item1'])
obj.quantity=request.POST['txt1']
obj.save()
if 'txt2' in request.POST:
if request.POST['txt2']!='':
obj=Item.objects.get(pk=request.POST['item2'])
obj.quantity=request.POST['txt2']
obj.save()
if 'txt3' in request.POST:
if request.POST['txt3']!='':
obj=Item.objects.get(pk=request.POST['item3'])
obj.quantity=request.POST['txt3']
obj.save()
#continue this code for all 6 items
update:
of course U can put this in a loop:
for i in range(1,6):
if 'txt'+str(i) in request.POST:
if request.POST['txt'+str(i)]!='':
obj=Item.objects.get(pk=request.POST['item'+str(i)]
obj.quantity=request.POST['txt'+str(i)]
obj.save()
in template:
<form method='POST' action=''>
{% for i in item %}
{{ i.name }}:<input type='text' id='txt{{forloop.counter}}' value='{{ i.quantity }}' /><input type='hidden' id='item{{forloop.counter}}' value='{{item.pk}}' /><input type='submit' value='increase' id='sbm{{forloop.counter}}' />
{% endfor %}
</form>
update: forloop.counter is the current for counter,1,2,3...