Styling ModelMultipleChoiceField Django - python

I'm having trouble styling my django form with a ModelMultipleChoiceField.
Heres my form:
class SkriptenSelect(forms.Form):
skripten = StyledModelMultipleChoiceField(
queryset=None,
widget=forms.CheckboxSelectMultiple,
)
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
super(SkriptenSelect, self).__init__(*args, **kwargs)
self.fields['skripten'].queryset = choices
class StyledModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return mark_safe('<ul class="list-inline" style="display: inline;">' \
'<li>{name}</li>' \
'<li>{semester}</li>' \
'<li>professor</li>' \
'</ul>'.format(name=escape(obj.name), semester=escape(obj.prefix))
)
And I used this for html:
<form action="." method="post">
<ul class="list-group">
{% for skript in result_form.skripten %}
<li class="list-group-item">
{{ skript }}
</li>
{% endfor %}
</ul>
{% csrf_token %}
<input type="submit" value="Submit Selected" />
This requires me to put my HTML in my forms file, which is not a very mvc way. Also it restricts me heavily in how i can style the list (i.e making table of the model instance fields)
Is there anyway to make this smarter? I'd like to access {{ skript.name }}, {{ skript.checkbox }} or something in my {% for skript in result_form.skripten %} loop, but thats sadly not possible...

You can use render_to_string. It loads a template and renders it with a context. Returns a string.
from django.template.loader import render_to_string
rendered = render_to_string('my_template.html', {'foo': 'bar'})
For various variants you can make various StyledModelMultipleChoiceField subclasses OR pass the desired template_name when initialising the class. Eg:
>>> class FooField:
... def __init__(self, template_name='default.html'):
... self.template_name = template_name
...
>>> x = FooField('table.html')
>>> x.template_name
'table.html'
Use self.template_name where appropriate:
def label_from_instance(self, obj)
return render_to_string(self.template_name, {'foo': obj.foo})
If you want to display multiple instances, use a formset.

Related

Django: DatePicker with "django-widget-tweaks"

Good day,
I'm trying "create" a DatePicker for one of my Inputfields in Django but it's not working!
In my models.py:
class Customer(models.Model):
...
name = models.CharField()
date = models.DateField()
In my views.py:
def Page(request):
CustomerFormSet = modelformset_factory(Customer, fields='__all__')
formset = CustomerFormSet (queryset=Customer.objects.none())
...
context = {'formset': formset}
return render(request, 'app/base.html', context)
In my template:
{% extends 'app/base.html' %}
{% load widget_tweaks %}
<form actions="" method="POST">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
...
{% render_field form.name class="form-control" %}
...
{% render_field form.date class="form-control" %}
...
Now my first Inputfield works fine! It returns a fitting Field in Bootstraps "Form-Group"-Layout. But my InputField for Dates remains a simple TextInput with no calendar apearing to choose from.
My Question is: am I doing something wrong or is it still impossible to obtain such a function in this way?
Thanks and a nice evening to all of you.
If you ara using ModelForm try:
from django import forms
class DateInput(forms.DateInput):
input_type = 'date'
class DataTreinoForm(forms.ModelForm):
class Meta:
model = models.YOURMODEL
fields = _all_
widgets = {
'dateField': DateInput
}
The default format is mm/dd/yyyy. I don't know how to change it in this way.
I just solved this too. Add type="date" to the render_field.
{% render_field form.date type="date" class="form-control" %}
You can add any input tag attributes here which is convenient because
Modify form appearance on the template instead of forms.py, which is conceptually consistent
When you save templates, it doesnt reload the app, so faster testing with html

How do I add custom actions to a change model form in Django Admin?

I'm working with Django admin, and I'd like to be able to use an existing instance of a model as a template for making a new object, so people using django admin don't need to re-key all the same properties on a object, when making new objects.
I'm picturing it a bit like this at the bottom of the Django admin form for updating a single object:
The django docs explain how to add bulk actions, by adding to the actions on a model, like so:
class ArticleAdmin(admin.ModelAdmin):
actions = ['make_published']
def make_published(self, request, queryset):
queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"
However, it wasn't so clear to me how to do this for a single change model form on an object, for actions I only want to apply to model at a time.
How would I do this?
I'm guessing I probably need to hack around with the change_model form, but beyond that, I'm not so sure.
Is there a fast way to do this without overriding loads of templates ?
Django Admin does not provide a way to add custom actions for change forms.
However, you can get what you want with a few hacking.
First you will have to override the submit row.
your_app/templates/admin/submit_line.html
{% load i18n admin_urls %}
<div class="submit-row">
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
{% if show_delete_link %}
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
<p class="deletelink-box">{% trans "Delete" %}</p>
{% endif %}
{% if show_save_and_copy %}<input type="submit" value="{% trans 'Create a new item based on this one' %}" name="_save_and_copy" />{% endif %}
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
</div>
In the above template, I just added the line {% if show_save_and_copy %}<input type="submit" value="{% trans 'Create a new item based on this one' %}" name="_save_and_copy" />{% endif %}. All other line are from default django implementation.
Then you will have to handle your button '_save_and_copy'
your_app/admin.py
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
class ArticleAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
"""We need to update the context to show the button."""
context.update({'show_save_and_copy': True})
return super().render_change_form(request, context, *args, **kwargs)
def response_post_save_change(self, request, obj):
"""This method is called by `self.changeform_view()` when the form
was submitted successfully and should return an HttpResponse.
"""
# Check that you clicked the button `_save_and_copy`
if '_save_and_copy' in request.POST:
# Create a copy of your object
# Assuming you have a method `create_from_existing()` in your manager
new_obj = self.model.objects.create_from_existing(obj)
# Get its admin url
opts = self.model._meta
info = self.admin_site, opts.app_label, opts.model_name
route = '{}:{}_{}_change'.format(*info)
post_url = reverse(route, args=(new_obj.pk,))
# And redirect
return HttpResponseRedirect(post_url)
else:
# Otherwise, use default behavior
return super().response_post_save_change(request, obj)
This example is for your specific case, it's up to you to make it more generic if you need to.
That being said, for your specific case you can also just click "Save and continue" to save your work, and then click "Save as new" to make a copy of it. Don't you ?
As pointed out, there is not a way and needs to be hacked. Here's I think an elegant hack for adding custom actions to both the list and change form views. It doesn't actually save the form just execute whatever custom action you want against the current object and return you back to the same change form page.
from django.db.models import Model
from django.contrib import admin, messages
from django.contrib.admin.options import (
unquote,
csrf_protect_m,
HttpResponseRedirect,
)
class ArticleAdmin(admin.ModelAdmin):
change_form_template = 'book/admin_change_form_book.html'
actions = ['make_published']
def make_published(self, request, queryset):
if isinstance(queryset, Model):
obj = queryset
obj.status = 'p'
obj.save()
updated_count = 1
else:
updated_count = queryset.update(status='p')
msg = "Marked {} new objects from existing".format(updated_count)
self.message_user(request, msg, messages.SUCCESS)
make_published.short_description = "Mark selected stories as published"
#csrf_protect_m
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
if request.method == 'POST' and '_make_published' in request.POST:
obj = self.get_object(request, unquote(object_id))
self.make_published(request, obj)
return HttpResponseRedirect(request.get_full_path())
return admin.ModelAdmin.changeform_view(
self, request,
object_id=object_id,
form_url=form_url,
extra_context=extra_context,
)
Now you can add an <input> for the action to the custom template view (this example uses book/admin_change_form_book.html in change_form_template)
{% extends 'admin/change_form.html' %}
{% block form_top %}
<input
type="submit"
name="_make_published"
value="Mark Published"
class="grp-button grp-default"
>
{% endblock %}
If you look at the admin/change_form.html (i.e. "django/contrib/admin/templates/admin/change_form.html") you can insert this custom <input> action anywhere between the <form...> </form> tags on the page. Including these blocks:
{% block form_top %}
{% block after_related_objects %}
{% block submit_buttons_bottom %}

django - What does this line achieve here?

I'm following a tutorial on effectivedjango.com, and this is the code they have:
views.py:
class CreateContactView(CreateView):
model = Contact
template_name = 'edit_contact.html'
fields = '__all__' #this is needed for error msg Using ModelFormMixin (base class of CreateContactView) without the 'fields' attribute is prohibited.
def get_success_url(self):
return reverse('contacts-list')
def get_context_data(self, **kwargs):
context = super(CreateContactView, self).get_context_data(**kwargs)
context['action'] = reverse('contacts-new')
return context
class UpdateContactView(UpdateView):
model = Contact
template_name = 'edit_contact.html'
fields = '__all__'
def get_success_url(self):
return reverse('contacts-list')
def get_context_data(self, **kwargs):
context = super(UpdateContactView, self).get_context_data(**kwargs)
context['action'] = reverse('contacts-edit', kwargs={'pk' : self.get_object().id})
return context
urls.py:
url(r'^$', contacts.views.ListContactView.as_view(),
name='contacts-list',),
url(r'^new$', contacts.views.CreateContactView.as_view(),
name='contacts-new',),
url(r'^edit/(?P<pk>\d+)/$', contacts.views.UpdateContactView.as_view(),
name='contacts-edit',),
contact_list.html:
{% block content %}
<h1>Contacts</h1>
<ul>
{% for contact in object_list %}
<li class="contact">
{{ contact }}
(edit)
</li>
{% endfor %}
</ul>
Add contact
{% endblock %}
edit_contact.html:
{% block content %}
{% if contact.id %}
<h1>Edit Contact</h1>
{% else %}
<h1>Add Contact</h1>
{% endif %}
<form action="{{ action }}" method="POST">
{% csrf_token %}
<ul>
{{ form.as_ul }}
</ul>
<input id="save_contact" type="submit" value="Save" />
</form>
Back to list
{% if contact.id %}
Delete
{% endif %}
{% endblock %}
Why does the line context['action'] = reverse('contacts-edit', kwargs={'pk' : self.get_object().id}) in views.py look like its calling itself?
What I mean is, this action is called when the submit button is pressed in the contact-edit template, correct? So it starts there, and it is reverse-calling contact-edit which is itself, right?
What am I not seeing here?
Thank you for all your help.
Yes, the line context['action'] = reverse('contacts-edit', kwargs={'pk' : self.get_object().id}) in views.py is calling itself. This line generates the proper url for contacts-edit view.
This is done so that POST requests come to the same view i.e. UpdateContactView which is an UpdateView. There, proper handling will be done i.e. form validation will occur with the sent data. If the form is valid, object will be updated. Otherwise, the form will be displayed again with errors.
Django docs on UpdateView:
A view that displays a form for editing an existing object,
redisplaying the form with validation errors (if there are any) and
saving changes to the object.

Making a function available to all models

I have the following function in one of my models
def get_fields(self):
return[(field.name, field.value_to_string(self)) for field in MusicPack._meta.fields]
Which helps me iterate over all the fields of a model and display them into a template. How would I implement this to all my models without replicating the two lines in every model in my database?
Would I just make a superclass that contain the function for all models and then have all my models children of it?
template.html
<div id = "subtemplate">
<ul>
{% for model in object_list %}
<div class = modeldiv>
{% for name,value in model.get_fields %}
<li>
{% if value %}
{{ name|capfirst }} : {{ value }}
{% endif %}
</li>
{% endfor %}
</div>
{% empty %}
<li> No objects created yet. </li>
{% endfor %}
</ul>
</div>
You could use Mixins.
Example
class Mixin(object):
def get_fields(self):
return[(field.name, field.value_to_string(self)) for field in self.__class__._meta.fields]
class A(models.Model, Mixin):
...
class B(models.Model, Mixin):
...
Putting that method in a base class is certainly one way to do it. Another approach is to have it be a generic utility function in some utility module that prints all fields in an object, which you can call before rendering your template.
from myproject.utilities import get_fields
...
return render_template("template.html", get_fields(model))
Yet a third approach is to write a class decorator which provides this functionality to a class:
class with_get_fields(cls):
def __init__(self, *args, **kwargs):
cls.__init__(self, *args, **kwargs)
def get_fields():
return [(field.name, field.value_to_string(self)) for field in cls.fields]
and then apply it to any model class that you wish to have this functionality
#with_get_fields
class model():
def __init___(self):
...

Django Template Page Outputting Nothing

Can someone help me figure out why my Django template pages won't render anything?
I'm using Python Requests (http://docs.python-requests.org/en/latest/) to retrieve JSON data from the external urls. I decode the data by using .json(). This works as I would expect when executing this from command line, but when it does nothing in the view.
When I run the server, the page is blank. It has no title, no 'testing' printed, nothing.
My Template:
<html>
<head><title>Offer List</title></head>
<body>
<p>Testing</p>
{% load dictparser %}
{% for offers in network1_offers %}
{% autoescape off %}
<div>
<p>name: {{ offers|lookup:"name" }}</p>
<p>pay: {{ offers|lookup:"payout" }}</p>
<p>description: {{ offers|lookup:"description" }}</p>
</div>
{% empty %}
<li>Sorry, no surveys available.</li>
{% endautoescape %}
{% endfor %}
</body>
</html>
My View:
class OffersList(View):
template_name="generic_app/offers.html"
def load_offers(request):
"""
import example network offers.
"""
user = request.user
user_agent = request.META['HTTP_USER_AGENT']
amparams = {'user_subid':user.sub_id, 'useragent':user_agent, 'user_ip':user.ip_address}
examplenetwork = requests.get('http://example.com/api-get.php?pubid=00000&key=000000000000&mode=offers&incent=1', data=amparams)
exampleoffers= examplenetwork.json()
"""
import example network 2 offers.
"""
clparams = {'subid':user.sub_id, 'ua':user_agent, 'geoip':user.ip_address}
examplenetwork2 = requests.get('http://www.examplenetwork2.com/blahblah', data=clparams)
exampleoffers2 = examplenetwork2.json()
render(request, 'generic_app/offers.html', {'network1_offers':exampleoffers, 'network2_offers':exampleoffers2})
The url:
url(r'^dashboard/offers$', OffersList.as_view(), name="offers"),
You're seeing this because you haven't defined how to get to the load_offers() method in your view, currently your load_offers() method is just a method floating around in your class.
Using the base class View comes with it's methods that you need to implement, for example
class OfferView(View):
template_name = "generic_app/offers.html"
def get(self, request, *args, **kwargs):
return load_offers(request)
or much better change this to a TemplateView(because that's what it really is).
class OfferView(TemplateView):
template_name = "generic_app/offers.html"
def get_context_data(self, **kwargs):
context = super(OfferView, self).get_context_data(**kwargs)
context['offers'] = load_offers(self.request)
return context

Categories