I have two models like this:
class ArtCollection(models.Model):
# nothing important in this case
class Artwork(models.Model):
...
name = models.CharField(max_length=200, null=True, blank=True)
art_collections = models.ManyToManyField("ArtCollection", related_name="artworks")
image = models.ImageField(...)
Recently, I've change the relationship between them from FK for ArtCollection on Artwork model to m2m (as seen as above). What I would like to have now is something that I had before, particulary ArtworkInline in admin panel on ArtCollection change view (editable artwork fields like name, image change and so on). But it doesn't work. The only solution I've came across is this one (I know I should make an image preview rather than display its name - but its just an example):
from inline_actions.admin import InlineActionsMixin, InlineActionsModelAdminMixin
class ArtworkInline(admin.StackedInline):
model = ArtCollection.artworks.through
extra = 0
fields = ['artwork_image']
readonly_fields = ['artwork_image']
def artwork_image(self, instance):
return instance.artwork.image
artwork_image.short_description = 'artwork image'
class ArtCollectionAdmin(InlineActionsModelAdminMixin, admin.ModelAdmin):
...
inlines = [ArtworkInline]
Is it possible to have the editable fields in m2m relationship in inline django panel? I also use grappeli and custom template for inline (which are useless after changing the relationship - they have worked pretty well with FK, now I can have only readable_fields on default template).
{% extends 'admin/stacked_inline.html' %}
{% block fieldset %}
<fieldset class="module aligned {{ fieldset.classes }}">
{% for line in fieldset %}
{% with forloop.counter as counter %}
{% for field in line %}
{{ field.errors }}
{% if counter == 2 or counter == 6 %}
{% elif counter <= 7 %}
<p>{{ field.field }}</p>
{% endif %}
{% endfor %}
{% endwith %}
{% endfor %}
</fieldset>
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}
<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>
{% endif %}
{% endblock %}
Thanks for any advices.
Related
In Django 1.11, I have 2 models, Foo and Bar:
class Foo(models.Model):
name = models.CharField()
class Bar(models.Model):
name = models.CharField()
foo = models.ForeignKey(Foo)
My admin.py looks like this:
class BarInline(admin.StackedInline):
model = Bar
template = 'admin/edit_inline/list.html'
class FooAdmin(admin.ModelAdmin):
fields = ('name')
inlines = [BarInline]
I use a customised template to show the Bar inline form, because I don't want the forms, just links to the edit pages for each Bar. list.html looks like this:
{% load i18n admin_urls static %}
<div class="js-inline-admin-formset inline-group" data-inline-type="stacked">
<fieldset class="module {{ inline_admin_formset.classes }}">
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
{{ inline_admin_formset.formset.management_form }}
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last %} empty-form last-related{% endif %}">
<h3 style="overflow:auto"><span style="float:left">{{ inline_admin_formset.opts.verbose_name|capfirst }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span><span style="float:right">{% if inline_admin_form.original %}Change Delete{% endif %}</span>
</h3>
</div>{% endfor %}
<div class="add-row">
Add a Bar
</div>
</fieldset>
</div>
The problem is that when I edit an existing Foo, and click Save, I get the error:
MultiValueDictKeyError at /admin/app/foo/1/change/
"'bar_set-0-id'"
EDIT: Stacktrace
If you don't want the form for the inline, the easiest approach is to not make the field(s) editable. This will render the objects with their values, but not editable in a form. The other way to display this is admin.TabularInline.
The options for inlines can be found here; https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#inlinemodeladmin-options
The option that you will likely want to enable is show_change_link
What you'd end up with is something like;
class BarInline(admin.StackedInline):
model = Bar
fields = ('name', )
readonly_fields = ('name', )
show_change_link = True
For anyone interested, I ended up extending the template. I created a file change_form.html in templates/admin/app/foo/, and overrode the after_related_objects block:
{% extends 'admin/change_form.html' %}
{% load admin_urls %}
{% block after_related_objects %}
<div class="js-inline-admin-formset inline-group">
<fieldset class="module">
{% for bar in original.bar_set.all %}
<h3>
{{ bar.name }}
Change
</h3>
{% endfor %}
</fieldset>
</div>
{% endblock %}
I have a Django Model Formset that is rendered with a crispy-forms in a custom table inline formset template. The formset is a list of alerts with entities, logic, and comments. Entity and logic are populated in each form, and the user writes a comment prior to submitting the form.
The formset currently has 140 forms (i.e. 140 records in the Alert model), and it will need to handle more than that number. It takes 4-5 minutes for alerts.html to render. I can use Django-based pagination to limit the queryset and reduce rendering time, but this precludes me from using something like jQuery datatables to paginate and quickly add JS functionality.
I used django-debug-toolbar to review. The queries are definitely funky and duplicated per form (i.e. the query grabbing entity information is duplicated for each form and also includes duplicate where statements). However, it only takes 3-4 seconds for the queries to run, so the problem must be elsewhere. I used Chrom dev tools to record the performance, and the Waiting (TTFB) time is the cause of the performance issue. I understand that this involves server-side processing, which leads me to believe it is a Django issue.
Why is the render taking so long? Is there anything in my code below that could be optimized? I know caching is an option, but I believe this mostly affects query performance which doesn't appear to be the driving issue. Is there any other piece of django-debug-toolbar that might shed more light on timing, outside of just the queries?
EDIT: Based on the comments, I determined the include tags in the table_inline_formset.html template are causing the issue because they are rendering hundreds of the same templates. I created another question here to address this problem: Crispy-Forms Include Tag Causing Many Duplicate Templates.
models.py:
class Logic(models.Model):
logic = models.CharField(max_length=50)
def __str__(self):
return self.logic
class Entity(models.Model):
entity = models.CharField(primary_key=True, max_length=12)
entityType = models.CharField(max_length=10)
entityDescription = models.CharField(max_length=200)
def __str__(self):
return '%s - %s - %s' % (self.entity, self.entityType, self.entityDescription)
class Alert(models.Model):
entity = models.ForeignKey(Entity, on_delete=models.CASCADE, db_column='entity')
logic = models.ForeignKey(Logic, on_delete=models.CASCADE, db_column='logic')
comment = models.CharField(max_length=500)
def __str__(self):
return '%s' % self.entity
forms.py:
AlertFormSet = modelformset_factory(Alert, extra=1, exclude=(), form=AlertForm)
class AlertFormsetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(AlertFormsetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.template = 'alerts/table_inline_formset.html'
self.add_input(Submit("submit", "Submit"))
self.layout = Layout(
Field('entity', css_class="input"),
Field('logic', css_class="input"),
Field('comment', css_class="input")
)
views.py:
def alerts(request):
newAlerts = Alert.objects.filter(disposition='')
formset = AlertFormSet(request.POST or None, queryset=newAlerts)
helper = AlertFormsetHelper()
context = {'formset':formset, 'helper':helper}
if request.method == 'POST':
for form in formset:
if form.is_valid():
if form.has_changed():
if form.is_valid():
form.save()
return HttpResponseRedirect('/alerts')
return render(request, 'alerts/alerts.html', context)
table_inline_formset.html:
{% load crispy_forms_tags %}
{% load crispy_forms_utils %}
{% load crispy_forms_field %}
{% specialspaceless %}
{% if formset_tag %}
<form {{ flat_attrs|safe }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% endif %}
{% if formset_method|lower == 'post' and not disable_csrf %}
{% csrf_token %}
{% endif %}
<div>
{{ formset.management_form|crispy }}
</div>
<div class='table-responsive'>
<table{% if form_id %} id="{{ form_id }}_table"{% endif%} class="table table-hover table-sm" id='dispositionTable'>
<thead>
{% if formset.readonly and not formset.queryset.exists %}
{% else %}
<tr>
{% for field in formset.forms.0 %}
{% if field.label and not field|is_checkbox and not field.is_hidden %}
<th for="{{ field.auto_id }}" class="form-control-label {% if field.field.required %}requiredField{% endif %}">
{{ field.label|safe }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</th>
{% endif %}
{% endfor %}
</tr>
{% endif %}
</thead>
<tbody>
{% for form in formset %}
{% if form_show_errors and not form.is_extra %}
{% include "bootstrap4/errors.html" %}
{% endif %}
<tr>
{% for field in form %}
{% include 'bootstrap4/field.html' with tag="td" form_show_labels=False %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "bootstrap4/inputs.html" %}
{% if formset_tag %}</form>{% endif %}
{% endspecialspaceless %}
alerts.html:
{% crispy formset helper %}
Is there a way to say to Django to hide/remove (show a blank space) for fields that got same values as previous row?
i.e.: if now is equal for differents Articles can it be show only for the first of the group?
from django.views.generic.list import ListView
from django.utils import timezone
from articles.models import Article
class ArticleListView(ListView):
model = Article
def get_context_data(self, **kwargs):
context = super(ArticleListView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context
<h1>Articles</h1>
<ul>
{% for article in object_list %}
<li>{{ article.pub_date|date }} - {{ article.headline }}</li>
{% empty %}
<li>No articles yet.</li>
{% endfor %}
</ul>
Article - now
a - 2017-01-01
b -
c - 2017-01-02
d -
Is this possible from view or directly in template?
You can use ifchanged which:
Checks if a value has changed from the last iteration of a loop.
as follows:
<h1>Articles</h1>
<ul>
{% for article in object_list %}
<li>{{ article.headline }} - {% ifchanged article.pub_date|date %}
{{ article.pub_date|date }} {% endifchanged %}
</li>
{% empty %}
<li>No articles yet.</li>
{% endfor %}
</ul>
This will check in each iteration the value of article.pub_date and only when that value changes it will be displayed.
Good luck :)
Models
class Ride(models.Model):
type = models.BooleanField(default=False)
ride_comment = models.TextField(null=True,max_length=140,blank=True)
def __unicode__(self):
return self.ride_comment
class Driver(models.Model):
ride_id = models.ForeignKey(Ride)
user_id = models.ForeignKey(User)
drv_carseats = models.SmallIntegerField(null=True,blank=False)
def __unicode__(self):
return self.user_id.username
Here is the Views.py
def search(request):
result_list = Ride.objects.all()
return render_to_response('rides/search.html', {'result_list':result_list}, context )
My Template:
{% for result in result_list %}
<li>
{% if result %}
{{ result.type }}
<em>{{ result.ride_comment }}</em>
{% endif %}
</li>
{% endfor %}
I want do display the user details in the template, ie user_id and username from the Driver model of that associated ride_id. I have no idea how to get this!
The way you have designed there is no direct way to access it. As its one-to-many type of relation.
though you can loop on result.driver_set.all and that will give you access to driver object access and you can fetch user_id access.
{% for result in result_list %}
<li>
{% if result %}
{{ result.type }}
<em>{{ result.ride_comment }}</em>
{% for item in result.driver_set.all %}
{{item.user_id}}
{% endfor %}
{% endif %}
</li>
{% endfor %}
Driver is actually the through table of a many-to-many relationship between Ride and User. You should make that explicit, by declaring a ManyToManyField on Ride:
users = models.ManyToManyField(User, through=Driver)
Now you can access that relationship more directly in the template:
{% for result in result_list %}
...
{% for user in result.users.all %}
{{ user.username }}
{% endif %}
Although I'd repeat what other comments have said: your relationship seems backwards, in that surely a ride should only have a single driver.
I have created a model, it will automatically display all the fields from the model and display it on the admin page.
Now, I have a problem, I would like to have two fields on the same line, to do this I have to specify the fieldsets at ModelAdmin:
fieldsets = (
(None, {
'fields': (('firstname', 'lastname'),)
}),
)
Do I have to specify all the fields? Because there are many fields in the database I need to specify.
Wrap those fields on their own tuple.
class TestAdmin(admin.ModelAdmin):
fields = (
'field1',
('field2', 'field3'),
'field4'
)
In the above example, fields field2 and field3 are shown on one line.
I'm afraid there's not an easy way to do it.
One option is to override the change_form.html template for that ModelAdmin and style the form as you like.
Another alternative is to do custom ModelForm and define a field with a widget that renders two input fields, in the form's .save() method, set the widget resulting value (a tuple) to both fields.
There is an article may be useful
http://amk1.wordpress.com/2010/09/23/a-2-column-django-admin-form/
Article is quote below:
Django is great. The bundled admin interface makes it better. But as the number of items on the form gets bigger, the amount of wasted space increases because the layout is single column. Coupled with left alignment on wide-screen monitors, my users usually end their day with a condition we call “eyeballs misalignment”.
So I improvised and changed the form (and StackedInline) to a 2-up layout. No more “eyeballs misalignment”.
The corresponding template for Django 1.2.1 (/contrib/admin/templates/admin/includes/fieldset.html) looks like this, modified lines highlighted:
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
<table border=0 width=100%>
{% for line in fieldset %}
{% cycle '<tr>' '' %}
<td width=50%>
<div style="border-bottom:0" class="form-row{% if line.errors %} errors{% endif %}{% for field in line %} {{ field.field.name }}{% endfor %}">
{{ line.errors }}
{% for field in line %}
<div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
{% if field.field.field.help_text %}
<p class="help">{{ field.field.field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
</div>
</td>
{% cycle '' '</tr>' %}
{% endfor %}
</table>
</fieldset>
this has worked for me
fieldsets=(
("My Group",{"fields": (tuple(['field1','field1']),),}),
)
It's stupid, but yes, if you're going to use the fieldsets tuple-within-a-tuple method, you have to then specify all the fields that should show on your form.
Agreed, that its annoying, but its tuple of tuples from list of fields.
you can use list comprehension and change list to tuple.
Here is an example for skipping some fields, that you want to give some special attention WHILE including rest normal way.
skipped=[]
alist = [field.name for field in <model_name>._meta.fields if field.name not in skipped]
fieldsets = tuple(alist)
*** play with skipped ***
with small tweaking this should work.