Django admin inline: Clone entry - django

I`m using a GenericStackedInline in my django admin. I would like to have the option to duplicate the inline object when editing the parent object.
I can think of two ways of doing this:
Using django-inline-actions to add a "clone" button. This did not work, because it does not show when using a fieldset in the GenericStackedInline
Adding another checkbox next to "delete" checkbox with label "clone". When activating the checkbox and saving parent object, it should clone the inline object with new id. Is there an easy way to add another checkbox and add a action to handle cloning?

Ok, found a solution. I don`t really like it. Biggest flaw in my oppinion is, that I did not find a way to do clone in InlineClass, Mixin or formset. It's a bit clumsy that two classes / Mixins need to be combined to handle it.
Improvements or other solutions are very welcome.
Inline classes
class RgCloneInlineFormset(BaseGenericInlineFormSet):
def add_fields(self, form, index):
super().add_fields(form, index)
form.fields["CLONE"] = forms.BooleanField(required=False)
class RgGenericStackedInlineWithClone(GenericStackedInline):
template = "admin/edit_inline/stacked_with_clone.html"
formset = RgCloneInlineFormset
class DetailpageElementInline(RgGenericStackedInlineWithClone, RgImagePreviewMixin):
model = DetailpageElement
extra = 0
Admin class
class CloneDetailpageElementInlineMixin():
def save_formset(self, request, form, formset, change):
# Save formset
ret = super().save_formset(request, form, formset, change)
# Do clone if nessesary
detailpage_ids_to_clone = []
for data in formset.cleaned_data:
if isinstance(data.get("id"), DetailpageElement):
if data.get("CLONE"):
detailpage_ids_to_clone.append(data.get("id").id)
for detailpage_element in DetailpageElement.objects.filter(id__in=detailpage_ids_to_clone):
detailpage_element.id = None
detailpage_element.save()
# Make sure function works like super function
return ret
#admin.register(ArticleInt)
class ArticleIntAdmin(CloneDetailpageElementInlineMixin, ContentAdminBase):
inlines = [
inlines.DetailpageElementInline,
]
Template admin/edit_inline/stacked_with_clone.html
Just copied the stacked.html from django source code and added
{% if inline_admin_formset.has_add_permission and inline_admin_form.original %}<span class="clone">{{ inline_admin_form.form.CLONE }} Clone</span>{% endif %}

Related

Custom field saving

I have a problem with Django admin, basically i have a model like team -> motorcycle connection, and now i have a lot of motorcycles and instead of inline objects i just put custom template with checkbox and manually added objects to database. Now for "hack" i have following line in my custom template:
<div id="divCheckbox" style="display: none;">
{{ inline_admin_formset.formset }}
</div>
and it works okay, but it takes a lot of time if there are many objects, if i remove the "hack" i get MultiValueDictKeyError. So for the saving in model i just overwrite save method, and save after the super.
def save_model(self, request, obj, form, change):
super(MotorcycleAdmin, self).save_model(request, obj, form, change)
moto_instert = Motorcycle()
moto_instert.benefit = obj
moto_instert.store = Motorcycle.get_by_team_id(id)
moto_instert.save()
So is there any way i can ignore this fields which i am saving manually or which way should i solve the problem?

django modelform property hidden field

I'm manually displaying my formset as a table, with each form being looped over. At the bottom of each form I include the hidden fields like:
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
But the problem is that I am also including properties in my form like:
class AllocationForm(forms.ModelForm):
name = forms.CharField(widget=forms.TextInput(attrs={'size': '15'}))
def __init__(self, *args, **kwargs):
super(AllocationForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['total_budgeted'] = self.instance.total_budgeted()
self.fields['total_budgeted_account_percent'] = self.instance.total_budgeted_account_percent()
self.fields['actual_spent'] = self.instance.actual_spent()
self.fields['actual_spent_account_percent'] = self.instance.actual_spent_account_percent()
self.fields['total_budgeted_category_percent'] = self.instance.total_budgeted_category_percent()
self.fields['actual_spent_category_percent'] = self.instance.actual_spent_category_percent()
class Meta:
model = Allocation
exclude = {'created', 'modified', 'source_account'}
And this works in the sense that I definitely see the properties being called, however they display as nothing so that's another issue.
The problem is when I keep the hidden fields in the template I will get errors such as 'int' object has no attribute 'get_bound_field' and so on depending on the return type of the property/method call.
My question is first: is there a check I can do to see if the field is a property in the template and therefore skip over it?
It may have something to do with how I'm using the property since in fact every property is displaying nothing (but I see it callback), so second would be about how to display the properties.
Well I am in the next step of the problem, but i have success in generating true form fields. In place of:
if self.instance:
self.fields['total_budgeted'] = self.instance.total_budgeted()
You can write:
if self.instance:
self.fields['total_budgeted'] = form.CharField(
initial=self.instance.total_budgeted(),
widget=HiddenInput()
)
In this code you I instantiate the form Field as CharField, you can use the FormField you want, and I hide it by choosing the Hidden input widget.

How can I tell if I'm doing a create or an update with django generic views (CreateView vs UpdateView) from my template?

I'm sharing the same template for my CreateView and UpdateView using django's generic views. I want the "submit" button in my template to say "Add" when I'm using the CreateView and "Update" when I'm using the UpdateView. Is there any way in my template to distinguish which view is being used (CreateView vs UpdateView)?
I know I could use a separate template using template_name_suffix and put the common stuff in a separate include or something but just wanted to see if there was a way to do it without creating a separate template.
When creating a new object, object will always be None, at the moment the template is rendered. You could check for the existence of {{ object }} in your template:
{% if object %}Update{% else %}Add{% endif %}
Override get_context_data and add a flag in your view:
def get_context_data(self, **kwargs):
context = super(YourClass, self).get_context_data(**context)
context['create_view'] = True
return context
Change YourClass to your Class View name
Then in your template you can:
{% if create_view %}

django admin: separate read-only view and change view

I'd like to use the django admin to produce a read-only view of an object which contains an "Edit" button which switches you to the usual change view of the same object.
I know how to use the readonly attributes to produces a read-only view, but I don't know how to produce two views, one read-only and one that allows changes.
I'd like to reuse as much of the admin interface for this as possible, rather than writing a view from scratch.
Note that this question isn't about permissions: all users will have permission to change the objects. It's just that I would prefer that they not use the change_view unless they do intend to make changes, reducing the risk of accidental changes or simultaneous changes.
Here's an answer that literally does what I asked with only a few lines of code and just a couple of template changes:
class MyModelAdmin(admin.ModelAdmin):
fieldsets = [...]
def get_readonly_fields(self, request, obj=None):
if 'edit' not in request.GET:
return <list all fields here>
else:
return self.readonly_fields
Now the usual URL for the change_form will produce a read only change_form, but if you append "?edit=1" to the URL, you will be able to edit.
The change_form template can also be customized depending on whether "?edit=1" is in the URL. To do this, put 'django.core.context_processors.request' in TEMPLATE_CONTEXT_PROCESSORS in settings.py, and then use request.GET.edit in the template.
For example, to add an "Edit" button when not in edit mode, insert
{% if not request.GET.edit %}
<li>Edit</li>
{% endif %}
just after <ul class="object-tools"> in change_form.html.
As another example, changing change_form.html to contain
{% if save_on_top and request.GET.edit %}{% submit_row %}{% endif %}
will mean that the submit row will only be shown in edit mode. One can also hide the Delete buttons on inlines, etc, using this method.
For reference, here is what I put in settings.py:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.contrib.messages.context_processors.messages',
# Above here are the defaults.
'django.core.context_processors.request',
)
I'd suggest to reconsider using custom views. With the help of generic DetailView, you'll need to write literally two lines of code. The template won't require much work either. You just extend standard change_form.html template, overriding field_sets block.
I know how to use the readonly attributes to produces a read-only view, but I don't know how to produce two views, one read-only and one that allows changes.
You actually can register one model in the admin twice[1], using proxy models. (There're some inconsistencies with permissions for proxy models, but it may not be a problem in your case.)
It seems to be possible to register multiple admin sites[2], too.
I'd like to reuse as much of the admin interface for this as possible, rather than writing a view from scratch.
Interface reuse as such has little to do with views, being mostly template- and style-related thing. View, however, should provide the template context necessary for interface reuse, as you correctly pointed out.
If you decide to go with multiple views per one ModelAdmin, then it might be useful for you to check how django-reversion project implements its admin integration: reversion/admin.py.
References
[1] Multiple ModelAdmins/views for same model in Django admin
[2] Registering Multiple Admin Sites
You will need to change template django admin uses for model form. Make it readonly and add a button to original template linked to another url.
Note:
I highly discourage this approach, you will definitely not prevent simultaneous changes. This should be solved with locking.
Also, I recommend using django-reversion for keeping history of objects and eliminating "accidental changes" risk.
You could create a custom view and display your object there.
To create a custom view in an admin module, override the get_urls() method :
class MyAdmin(admin.ModelAdmin):
…
def get_urls(self):
urls = super(MyAdmin, self).get_urls()
my_urls = patterns('',
url(r'^custom_view/(?P<my_id>\d+)/$', self.admin_site.admin_view(self.custom_viem), name='custom_view')
)
return my_urls + urls
def custom_view(self, request, my_id):
"""Define your view function as usual in views.py
Link to this view using reverse('admin:custom_view')
"""
from myapp import views
return views.custom_view(request, my_id, self)
In views.py :
def custom_view(request, object_id, model_admin):
admin_site = model_admin.admin_site
opts = model_admin.model._meta
my_object = get_object_or_404(MyObject, pk=object_id)
# do stuff
context = {
'admin_site': admin_site.name,
'opts': opts,
'title': _('My custom view'),
'root_path': '%s' % admin_site.root_path,
'app_label': opts.app_label,
'my_object': my_object,
}
return render_to_response('my_template.html', context,
context_instance=RequestContext(request))
In your template, use {% extends "admin/base_site.html" %} to keep the admin look and feel.
The below code is implementation of read-only admin using proxy models.
Models.py
//real model
class CompetitionEntry(models.Model):
pass
//Proxy model
class ReviewEntry(CompetitionEntry):
class Meta:
proxy = True
def save(self, *args, **kwargs):
pass
admin.py
//editable admin
class CompetitionEntryAdmin(admin.ModelAdmin):
pass
admin.site.register(CompetitionEntry, CompetitionEntryAdmin)
// read-only admin (assign only "change" permission for this)
class ReviewEntryAdmin(admin.ModelAdmin):
pass
admin.site.register(ReviewEntry, ReviewEntryAdmin)

Add a prefix do Django comment form

I would like to add a prefix to each django comment form. I'm using multiply comment forms in the same page and depsite it's working well, i don't like having many input fields with the same id attribute like <input type="text" name="honeypot" id="id_honeypot" />.
So, is there a way to tell django do add a prefix to each form instance? I know i can do it with other forms when i create a form instance in this waynewform = CustomForm(prefix="a") but using Django's comment system, this part is handled by a comment template tag {% get_comment_form for [object] as [varname] %}.
Can I tell to the template tag to add a prefix?
Well, I have an idea. Add your custom comments form and override __init__. You can generate prefix from target_object and set it to self.prefix:
def __init__(self, target_object, data=None, initial=None):
...
Or better, override BaseForm.add_prefix:
def add_prefix(self, field_name):
"""
Returns the field name with a prefix appended, if this Form has a
prefix set.
Subclasses may wish to override.
"""
return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
Update:
Yes, you're right. Prefix wouldn't work, the main reason is the code in contrib.comments.views.comments.post_comment. So I've reread your question and if you only need to change "id" attribute use BaseForm.auto_id:
class CustomCommentForm(CommentForm):
def __init__(self, target_object, data=None, initial=None):
super(CustomCommentForm, self).__init__(target_object, data, initial)
idprefix = target_object.__class__.__name__.lower()
self.auto_id = idprefix + "_%s"