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 %}
Related
I'm trying to add an edit form to an existing model, but it does not save every time and redirects me to the home page instead of the 'account' page. What am I doing wrong? why changes in the existing model are not visible? any help will be appreciated.
views.py
def account(request):
data_now = datetime.datetime.now().strftime("%Y-%m-%d")
#my form
time = get_object_or_404(Time, pk=52)
if request.method == "POST":
form = TimeEditForm(request.POST, instance=time)
if form.is_valid():
time = form.save(commit=False)
time.save()
return redirect('account')
else:
form = TimeEditForm(instance=time)
context = {'data_now': data_now, 'time_edit_form': form}
return render(request, 'account.html', context)
forms.py
class TimeEditForm(forms.ModelForm):
class Meta:
model = Time
fields = ('compartment',)
labels ={
'free_or_no': 'field name in my language?'
}
models.py
class Time(models.Model):
day_time = models.ForeignKey(DayTime, on_delete=models.CASCADE)
compartment = models.CharField(max_length=11)
free_or_no = models.BooleanField(default=True)
time_equivalent = models.IntegerField()
urls.py
urlpatterns = [
url(r'^$', views.masseur_detail, name='masseur_detail'),
url(r'^account$', views.account, name='account')
]
account.html
<form action="." method="post">
{% csrf_token %}
{{ time_edit_form|crispy }}
<button type="submit" class="btn btn-block btn-primary"> Save</button>
</form>
This is quite a subtle issue.
Usually in Django it's recommended to use URLs that end with a slash - eg "/account/" - but your URL is just "/account", without the slash.
Now, when you put action="." in your form, the browser interprets this as "post to the root of the current directory". If your URL did end with a slash, that would be resolve to the same page. But because it doesn't, the browser posts to the root itself, ie "/".
The best solution is to change your URL pattern to r'^account/$'; alternatively (but not recommended) you could change your form to use action="".
I'm writing a sports league app. I get a drop down list of all my teams. And I can display the wins/losses data on a separate view. But I can't get the StandingsView (with the form) to correct redirect to the TeamView (display information).
I've tried both POST and GET. I've run into a bunch of issues I don't understand. First, the url from the form is directed to
/teamview/?team_name=6
I don't understand why that is, even if my view specifies otherwise.
Second, the view doesn't redirect unless I do so in the form action. I think that's a product of the GET function, but I'm not sure. I'm hesitant to use POST because I'm not changing the DB
I've looked into RedirectView, but worry (as always) I'm overcomplicating this.
Thank you much,
Views.py
class StandingsView(FormView):
form_class = SelectTeam
template_name = 'teamsports/standings.html'
model = Teams
success_url = '/teamview/'
def form_valid(self, form):
team = form.cleaned_data['team']
return redirect('teamview', team = team)
def form_invalid(self,form):
HttpResponse ("This didn't work")
def TeamView(request, team):
try:
team = Teams.objects.filter(team=team).values()
except Teams.DoesNotExist:
raise Http404("Team does not exist")
return render(request, 'teamview.html', {'team':team})
urls.py
urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^admin/', admin.site.urls),
url(r'^standings/$', views.StandingsView.as_view(), name="standings"),
url(r'teamview/(?P<team>[0-9]+)/$', views.TeamView, name="teamview")
forms.py
class SelectTeam(ModelForm):
team_name = forms.ModelChoiceField(queryset=Teams.objects.all(), initial=0)
class Meta:
model = Teams
fields = ['team', 'team_name']
standings.html
<form action= "/teamview/" method="GET">
{{ form }}
<input type="submit" value="Submit" />
{{ form.errors }}
</form>
{% endblock %}
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()
I have a django Formset that I'd like to layout in the middle of another form. I'm using django-crispy-forms to set the layout in the parent form's __init__:
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout, Field, Div
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Div(
Div(Field('foo'), css_class='span3'),
Div(Field('bar'), css_class='span4'),
css_class='row'
),
Field('baz', css_class='span1'),
...
)
self.helper.add_input(Submit('submit', 'Submit', css_class='btn btn-primary offset4'))
My template simply renders the form using the {% crispy %} tag.
I'd like to know how I should incorporate the formset. Should I instantiate it in the above init function? How do I refer to it there?
There are other examples of form and formset combos online that have one render after the other serially, but I'm wondering whether I can have more control over how they fit together with crispy's layout.
I solved this without modifying Crispy Forms, by creating a new field type that renders a formset:
from crispy_forms.layout import LayoutObject, TEMPLATE_PACK
class Formset(LayoutObject):
"""
Layout object. It renders an entire formset, as though it were a Field.
Example::
Formset("attached_files_formset")
"""
template = "%s/formset.html" % TEMPLATE_PACK
def __init__(self, formset_name_in_context, template=None):
self.formset_name_in_context = formset_name_in_context
# crispy_forms/layout.py:302 requires us to have a fields property
self.fields = []
# Overrides class variable with an instance level variable
if template:
self.template = template
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
formset = context[self.formset_name_in_context]
return render_to_string(self.template, Context({'wrapper': self,
'formset': formset}))
It needs a template to render the formset, which gives you control over exactly how it's rendered:
{% load crispy_forms_tags %}
<div class="formset">
{% crispy formset %}
<input type="button" name="add" value="Add another" />
</div>
You can use it to embed a formset in your layouts just like any other Crispy layout element:
self.helper.layout = Layout(
MultiField(
"Education",
Formset('education'),
),
A slight modification to the earlier answer by qris.
This update (as suggested by Alejandro) will allow for our custom Formset Layout Object to use a FormHelper object to control how the formset's fields are rendered.
from crispy_forms.layout import LayoutObject
from django.template.loader import render_to_string
class Formset(LayoutObject):
"""
Renders an entire formset, as though it were a Field.
Accepts the names (as a string) of formset and helper as they
are defined in the context
Examples:
Formset('contact_formset')
Formset('contact_formset', 'contact_formset_helper')
"""
template = "forms/formset.html"
def __init__(self, formset_context_name, helper_context_name=None,
template=None, label=None):
self.formset_context_name = formset_context_name
self.helper_context_name = helper_context_name
# crispy_forms/layout.py:302 requires us to have a fields property
self.fields = []
# Overrides class variable with an instance level variable
if template:
self.template = template
def render(self, form, form_style, context, **kwargs):
formset = context.get(self.formset_context_name)
helper = context.get(self.helper_context_name)
# closes form prematurely if this isn't explicitly stated
if helper:
helper.form_tag = False
context.update({'formset': formset, 'helper': helper})
return render_to_string(self.template, context.flatten())
Template (used to render formset):
{% load crispy_forms_tags %}
<div class="formset">
{% if helper %}
{% crispy formset helper %}
{% else %}
{{ formset|crispy }}
{% endif %}
</div>
Now it can be used in any layout just like any other crispy forms layout object.
self.helper.layout = Layout(
Div(
Field('my_field'),
Formset('my_formset'),
Button('Add New', 'add-extra-formset-fields'),
),
)
# or with a helper
self.helper.layout = Layout(
Div(
Field('my_field'),
Formset('my_formset', 'my_formset_helper'),
Button('Add New', 'add-extra-formset-fields'),
),
)
This is currently not supported in crispy-forms. Your only option would be to use |as_crispy_field filter (not documented yet, sorry).
I have started development of this feature for {% crispy %} tag and in a feature branch, it's all explained here: https://github.com/maraujop/django-crispy-forms/issues/144
I'm looking for feedback, so if you are still interested, feel free to post.
Basing on above solution Formset(LayoutObject), you would combine django-dynamic-formset & crispy.
On my order page I have:
order's section part 1
order's inline formset with dynamic-add forms
order's section part N
Now it is simple and clear, ModelForms are:
class OrderTestForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(OrderTestForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_tag = True
self.helper.html5_required = True
self.helper.form_action = 'test_main'
self.helper.layout = Layout(
'product_norms', #section 1
'reference_other', #section 1
# rest of the section 1 fields
Formset('samples', 'helper'), # inline dynamic forms
'checkbox_is_required' # start of section N
# other order sections fields
)
self.helper.add_input(Submit("submit", "Save order"))
Formset helper layout:
class SamplesFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(SamplesFormSetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.html5_required = True
self.layout = Layout(
Fieldset('',
'description',
'product', # foreign key
'DELETE', # delete django-dynamic-formset
css_class="formset_row"), # add-rows
)
self.form_tag = False
self.render_required_fields = False
Add/delete inlines, saving order with formset operations work as expected.
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>