I have the following 3 models, each of which has a 1-many relationship with it's children
* Experiment
* Site (lab, schools, etc.)
* Participants
Since a Site might have hundreds of participants, I've overridden it's models change_form.html, where I've added a 'bulk create' form in the 'after_field_sets' block.
Here's my my-project/templates/admin/my-app/site/change_form.html:
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}
{% block after_field_sets %}
{{ block.super }}
<h3>Bulk Create Participants</h3>
<form method="POST">
<input type="hidden" name="action" value="create"/>
<label>First uid (inclusive) <input type="text" name="firstUid"/></label>
<label>Last uid (inclusive) <input type="text" name="lastUid"/></label>
<label>Condition Order<input type="text" name="conditionOrder"/></label>
</form>
<hr/>
{% endblock %}
If I do nothing else, everything gets displayed properly, however without a custom view, I don't have any way of processing the custom form. When I add a get_urls() and a custom view to my SiteAdmin, which should allow me to process the custom form, only the 'after_field_sets' block is displayed.
Here's my Site ModelAdmin:
class SiteAdmin(admin.ModelAdmin):
fields = ['experiment', 'name', 'description']
readonly_fields = ['experiment']
inlines = [ParticipantInline]
def get_urls(self):
urls = super(SiteAdmin, self).get_urls()
my_urls = [
url(r"^(?P<pk>[0-9]+)/$", self.admin_site.admin_view(self.my_view,
cacheable=True)),
]
return my_urls + urls
def my_view(self, request):
context = dict(
self.admin_site.each_context(request),
opts = Site._meta,
change = True,
is_popup=False,
save_as=False,
has_delete_permission=False,
has_add_permission=False,
has_change_permission=False
)
if request.method == 'POST':
action = request.POST["action"]
firstUID = request.POST["firstUid"]
lastUID = request.POST["lastUid"]
if "create" == action:
for uid in range(firstUID, lastUID+1):
Participant.objects.create(site=site, uid=uid)
return TemplateResponse(request, "admin/experimentAdmin/site/change_form.html", context)
Is there more information I need to pass to the context to get the entire form to display?
Any help would be appreciated.
It turned out that a better way to do this was to not to override the change_form.html template, but rather create a ModelForm for my Site model, where I defined some extra fields (not part of the actual model), set required=False on these fields, added them to the ModelAdmin's fieldset, and handled the optional extra form fields in my SiteAdmin::save_model()
class SiteForm(forms.ModelForm):
first_uid = forms.IntegerField(label='First UID', required=False)
last_uid = forms.IntegerField(label='Last UID', required=False)
conditions_order = forms.CharField(label='Conditions Order', required=False)
class Meta:
model = Site
fields = ("name", "description",
"first_uid", "last_uid", "conditions_order")
class SiteAdmin(admin.ModelAdmin):
form = SiteForm
inlines = [ParticipantInline]
fieldsets = (
(None, {
"fields": ("name", "description")
}),
("Bulk Create Participants", {
"fields": ("first_uid", "last_uid",
"conditions_order")
})
)
def save_model(self, request, obj, form, change):
# deal with optional fields if present
...
obj.save()
Related
I am trying to display a ModelForm with prepopulated instance data.
It works fine except for the ChoiceField which always displays the first choice given in forms.py ('LP') rather than the choice provided by the instance.
View:
def review(request):
order = Order.objects.get(user__pk=request.user.id)
form = ProjectReviewForm(instance=order)
context = {
'form': form,
}
return render(request, 'users/projectreview.html', context)
Forms:
class ReviewForm(forms.ModelForm):
LAND = 'LP' // #INSTANCE ALWAYS SHOWS THIS RATHER THAN INSTANCE DATA
DATA = 'DC'
STATIC = 'SW'
CHOICES = (
(LAND, 'LP'),
(DATA, 'DC'),
(STATIC, 'SW')
)
product = forms.ChoiceField(choices=CHOICES, widget=forms.Select(attrs={'class': 'form-field w-input'}),)
class Meta:
model = Order
fields = '__all__'
template:
<form method="POST" class="contact-form">
{% csrf_token %}
<h2 class="form-heading-small">Please make sure everything you've submitted is correct.</h2>
{{ form }}
<button type="submit" data-wait="Please wait..." class="button w-button">Looks good!</button>
</form>
The product field on the form is overriding the field on the model. Look into using a ModelChoiceField
Models:
class Instructional_Cycle(models.Model):
date_started = models.DateField()
date_finished = models.DateField()
standard_tested = models.OneToOneField(Standard, on_delete=models.CASCADE)
class Standard(models.Model):
subject = models.CharField(max_length=14, choices=subjects)
grade_level = models.IntegerField(choices=gradeLevels)
descriptor = models.CharField(max_length=15)
description = models.TextField()
essential_status = models.BooleanField(default=False)
View:
class CycleCreateView(CreateView):
model = Instructional_Cycle
template_name = 'cycle_new.html'
fields = '__all__'
success_url = reverse_lazy('student_progress:cycles')
Template:
<!-- student_progress/cycle_new.html -->
{% extends 'base.html' %}
{% block content %}
<h1>Add a new instructional cycle:</h1>
<form action="{% url 'student_progress:cycle_new' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">add cycle</button>
</form>
{% endblock content %}
The problem I'm having with this form is that the dropdown to select Instructional_Cycle.standard_tested has literally 1000 records from Standard. There's no way that the user can scroll through all of those and find the one record they want.
What I need is some way to click a link and filter the dropdown list by subject or grade_level and/or a search box, similar to what's achieved on the admin side by creating a custom admin model in admin.py like so:
class StandardAdmin(admin.ModelAdmin):
list_display = ('descriptor', 'description', 'essential_status')
list_filter = ('subject', 'grade_level', 'essential_status')
search_fields = ('descriptor',)
inlines = [MilestoneInLine]
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
try:
search_term_as_int = int(search_term)
except ValueError:
pass
else:
queryset |= self.model.objects.filter(age=search_term_as_int)
return queryset, use_distinct
Please "dumb it down" for this newbie. I just finished working through Django for Beginners, and my conceptual model of how this all fits together is still full of holes. Please assume that I know hardly anything. Thanks!
That amount of reactive work on one page will require you to be comfortable with Javascript, Ajax, etc. If that is the case, there are a number of approaches you could take that let you refresh the form with the desired options.
Alternatively, you could ask the user for the necessary data one step earlier in the process and let Django build the correct form for you in the first place by overriding the form's default queryset.
You should look into using something like django-ajax-select. https://github.com/crucialfelix/django-ajax-selects
I am building a site which uses userena for the profile and registration part. The problem is that I am trying to remove the mugshot upload part and the profile privacy(registered,open,closed) from edit profile page so that userena uses gravatar only and the profiles are public for all. But in the template there is just
<fieldset>
<legend>{% trans "Edit Profile" %}</legend>
{{ form.as_p }}
</fieldset>
<input type="submit" value="{% trans "Save changes" %}" />
</form>
I am trying to find out how to edit this or the views to remove the mugshot and privacy from the form but without success. Please help?
Instead of editing userena forms directly you should sub it in your own forms.py file (accounts/forms.py for example) as mentioned in the FAQ and put the url above the userena include. Here is an example where I use crispy-forms to sub class the edit profile form for nice bootstrap forms:
accounts/forms.py
class EditProfileFormExtra(EditProfileForm):
class Meta:
model = get_profile_model()
exclude = ['user', 'mugshot', 'privacy', 'my_custom_field']
def __init__(self, *args, **kwargs):
super(EditProfileFormExtra, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'edit-profile-form'
self.helper.form_class = 'form-horizontal'
self.helper.form_method = 'post'
self.helper.help_text_inline = True
self.helper.add_input(Submit('submit', _('Save'), css_class='green'))
self.helper.layout = Layout(
Field('first_name', placeholder=_("First Name")),
Field('last_name', placeholder=_("Last Name")),
Field('language', css_class="chosen"),
Field('timezone', css_class="chosen"),
)
accounts/urls.py
urlpatterns = patterns(
'',
url(r'^signup/$', 'userena.views.signup', {'signup_form': SignupFormExtra}, name='signup'),
url(r'^signin/$', 'userena.views.signin', {'auth_form': SigninFormExtra}, name='signin'),
url(r'^(?P<username>[\.\w-]+)/edit/$', 'userena.views.profile_edit', {'edit_profile_form': EditProfileFormExtra}, name='edit-profile'),
url(r'^', include('userena.urls')),
)
You can do this with just about any form as you can see in the urls above. Basically it says at this url, use the original modules view, but replace the form argument with my own form.
The best is to remove those two fields by editing the form itself. In the view.py of the userena package, simply change the EditProfileForm by adding 'mugshot' and 'privacy' to the exclude list:
class EditProfileForm(forms.ModelForm):
...
class Meta:
model = get_profile_model()
exclude = ['user', 'mugshot', 'privacy']
If you really want to change the template only, you can iterate through the form instead of using form.as_p. In this case you will have to add the markup for other field parameters (like labels, errors, non-field errors etc) - see an example a here.
{% for field in form %}
{% if field.name != 'mugshot' %}
{{ field }}
{% endif %}
{% endfor %}
I am building a webapp which will be used by a company to carry out their daily operations. Things like sending invoices, tracking accounts receivable, tracking inventory (and therefore products). I have several models set up in my various apps to handle the different parts of the web-app. I will also be setting up permissions so that managers can edit more fields than, say, an office assistant.
This brings me to my question. How can I show all fields of a model and have some that can be edited and some that cannot be edited, and still save the model instance?
For example, I have a systems model for tracking systems (we install irrigation systems). The system ID is the primary key, and it is important for the user to see. However, they cannot change that ID since it would mess things up. Now, I have a view for displaying my models via a form using the "form.as_table". This is efficient, but merely spits out all the model fields with input fields filling in the values stored for that model instance. This includes the systemID field which should not be editable.
Because I don't want the user to edit the systemID field, I tried making it just a label within the html form, but django complains. Here's some code:
my model (not all of it, but some of it):
class System(models.Model):
systemID = models.CharField(max_length=10, primary_key=True, verbose_name = 'System ID')
systemOwner = models.ForeignKey (System_Owner)
installDate = models.DateField()
projectManager = models.ForeignKey(Employee, blank=True, null=True)
#more fields....
Then, my view for a specific model instance:
def system_details(request, systemID):
if request.method == 'POST':
sysEdit = System.objects.get(pk=systemID)
form = System_Form(request.POST, instance=sysEdit)
if form.is_valid():
form.save()
return HttpResponseRedirect('/systems/')
else:
sysView = System.objects.get(pk=systemID)
form = System_Form(instance=sysView)
return render_to_response('pages/systems/system_details.html', {'form': form}, context_instance=RequestContext(request))
Now the html page which displays the form:
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Save Changes">
<input type="button" value="Cancel Changes" onclick="window.location.href='/systems/'">
</form>
So, what I am thinking of doing is having two functions for the html. One is a form for displaying only those fields the user can edit, and the other is for just displaying the content of the field (the systemID). Then, in the view, when I want to save the changes the user made, I would do:
sysValues = System.objects.get(pk=SystemID)
form.save(commit = false)
form.pk = sysValues.sysValues.pk (or whatever the code is to assign the sysValues.pk to form.pk)
Is there an easier way to do this or would this be the best?
Thanks
One thing you can do is exclude the field you don't need in your form:
class System_Form(forms.ModelForm):
class Meta:
exclude = ('systemID',)
The other is to use read-only fields: http://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields as #DTing suggessted
To make a field read only you can set the widget readonly attribute to True.
using your example:
class System_Form(ModelForm):
def __init__(self, *args, **kwargs):
super(System_Form, self).__init__(*args, **kwargs)
self.fields['systemID'].widget.attrs['readonly'] = True
class Meta:
model = System
or exclude the fields using exclude or fields in the class Meta of your form and display it in your template if desired like so:
forms.py
class System_Form(ModelForms):
class Meta:
model = System
exclude = ('systemID',)
views.py
def some_view(request, system_id):
system = System.objects.get(pk=system_id)
if request.method == 'POST':
form = System_Form(request.POST, instance=system)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = System_Form(instance=system)
context = { 'system':system,
'form':form, }
return render_to_response('some_template.html', context,
context_instance=RequestContext(request))
some_template.html
<p>make changes for {{ system }} with ID {{ system.systemID }}</p>
<form method='post'>
{{ form.as_p }}
<input type='submit' value='Submit'>
</form>
Hey,
I'm using a model formset to let my users edit their photo album. I want to put a Radio select box on every photo saying "Set as cover image" so that I can process all the photos and find the one who should be album cover. The problem is how can I a field with radio select on to the formset and still keep it mutal with the rest of the photos? This is my current code:
class ProjectGalleryForm(forms.ModelForm):
remove_photo = forms.BooleanField()
# set_as_cover_image = .... ?? <-- what to put?
class Meta:
model = Photo
exclude = (
'effect',
'caption',
'title_slug',
'crop_from',
'is_public',
'slug',
'tags'
)
I think the key here is that the radio button is not actually part of the formset: it's part of the parent form. It's the actual Album model that needs to know which of the Photo objects is the cover image. So what you want to do is to display each option from the radio button alongside its corresponding line in the Photo formset - and that's the tricky bit, because Django can't render form fields in that way. You'll need to produce the HTML for each option manually.
So, given these forms, and assuming the Album model has a cover_image which is a OneToOneField to Photo:
class AlbumForm(forms.modelForm):
class Meta:
model = Album
photo_formset = forms.inlineformset_factory(Album, Photo, form=ProjectGalleryForm)
in the template you would do something like:
{% for photo_form in photo_formset %}
<tr><td>
{% if photo_form.instance.pk %}
<input type="radio" id="id_cover_image_{{ forloop.counter }}" name="cover_image" value="{{ photo_form.instance.pk }}">
<label for="id_cover_image_{{ forloop.counter }}">Use as cover image</label>
{% endif %>
</td><td>{{ photo_form.as_p }}</td>
</tr>
{% endfor %}
I like to have the a neat template file and therefore, I made a custom widget for this purpose.
class SingleRadioInput(Input):
input_type = 'radio'
def render(self, value, checked, attrs=None):
output = []
if value:
is_cover = ''
if checked : is_cover = 'checked'
output.append(
('<input type="radio" name="inline" value="%s" %s/>')
% (value, is_cover)
)
return mark_safe(u''.join(output))
Hope it can help someone
Based on #Mikou answer, here is my more comprehensive solution.
In order to keep my template clean and pretty, I used a custom widget
class SingleRadioInput(forms.widgets.Input):
input_type = 'radio'
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type=self.input_type)
output = []
if name:
is_checked = ''
if value:
is_checked = 'checked'
output.append(
('<input id="%s" type="radio" name="%s" value="%s" %s/>')
% (final_attrs['id'], final_attrs['name'], final_attrs['instance_id'], is_checked )
)
return mark_safe(u''.join(output))
My object form looks like that, it will auto select the object if the field default == True
class ObjectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ObjectForm, self).__init__(*args, **kwargs)
self.fields['default'].widget.attrs.update({'instance_id': self.instance.id, 'name': 'default'})
if self.instance.default:
self.fields['default'].widget.attrs.update({'value': True})
class Meta:
model = MyModel
fields = ['default']
widgets = {
'default': SingleRadioInput(),
}
Here is my formset
ProductReferenceFormset = inlineformset_factory(ParentModel, MyModel,
form=ObjectForm,
extra=0, can_delete=False, can_order=False)
I gave up handling the save part in the form, it is really not worth the complexity I think... So the save part is in the form_valid() in the View
def form_valid(self, form, price_form):
form.save()
# save the default radio
MyModel.objects.filter(parent=self.object).update(default=False)
MyModel.objects.filter(id=self.request.POST.get('default')).update(default=True)
return HttpResponseRedirect(self.get_success_url())
Qualification:
<option value='10th' {% if '10th' in i.qf %} selected='select' {% endif %}>10th</option>
<option value='12th' {% if '12th' in i.qf %} selected='select' {% endif %}>12th</option>
<option value='graduted' {% if 'Graduated' in i.qf %} selected='select' {% endif %}>Graduated</option>
</select>
<br><br>