Custom field saving - django

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?

Related

Django admin inline: Clone entry

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 %}

Forms, Model, and updating with current user

I think this works, but I came across a couple of things before getting it to work that I want to understand better, so the question. It also looks like other people do this a variety of ways looking at other answers on stack overflow. What I am trying to avoid is having the user to have to select his username from the pulldown when creating a new search-profile. The search profile model is:
class Search_Profile(models.Model):
author_of_profile = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=True)
keyword_string = models.CharField(max_length=200)
other_stuff = models.CharField(max_length=200)
The form I ended up with was:
class Search_Profile_Form(ModelForm):
class Meta:
model = Search_Profile
fields = [ 'keyword_string', 'other_stuff']
Where I deliberately left out 'author_of_profile' so that it wouldn't be shown or need to be validated. I tried hiding it, but then the form would not save to the model because a field hadn't been entered. If I was o.k. with a pulldown I guess I could have left it in.
I didn't have any issues with the HTML while testing for completeness:
<form action="" method="POST">
{% csrf_token %}
{{ form.author_of_profile}}
{{ form.keyword_string }}
{{ form.other_stuff }}
<input type="submit" value="Save and Return to Home Page">
</form>
And the View is where I ended up treating the form and the model separated, saving the form first, then updating the model, which is where I think there might be some other way people do it.
def New_Profile(request):
if request.method=='POST':
form = Search_Profile_Form(request.POST)
if form.is_valid():
post=form.save(commit=False)
# here is where I thought I could update the author of profile field somehow with USER
# but If I include the author_of_profile field in the form it seems I can't.
post.save()
#So instead I tried to update the author_of profile directly in the model
current_profile=Search_Profile.objects.last()
current_profile.author_of_profile=request.user
current_profile.save()
return(redirect('home'))
else:
form=Search_Profile_Form()
return render(request, 'mainapp/New_Profile.html', {'form': form})
So a few questions:
For the Foreign Key in author_of_profile field, is it better to use blank=True, or null=True
I ended up using request.user rather than importing from django.contrib.auth.models import User is there any difference?
My real question though, is the above a reasonable way to get form data and update the database with that data and the user? Or am I missing some other way that is more build in?
post=form.save()
current_profile.author_of_profile=request.user
post.save()
return(redirect('home'))
try something like this. save the form to db then change the author again. save(commit=False) will not save the date to db immediately.

Using a radio button across forms in a formset in django

I have a page, where I display different forms using formsets.
These are my models:
class Phone(models.Model):
mac = models.CharField()
class PhoneLine(models.Model):
phone = models.ForeignKey(Phone)
voicemail = models.BooleanField("Voice Mail", default=False)
A phone can have many lines (phoneline), but only one phone line can have voice mail enabled.
By default when I display phonelines using formset, the voice mail field displays as a check box as follows.(I have added prefix to the formset, based on requirements)
form1 of the formset
<input type="checkbox" name="phone_client_line_1-0-voicemail" id="id_phone_client_line_1-0-voicemail">
form2 of the formset
<input type="checkbox" name="phone_client_line_1-1-voicemail" id="id_phone_client_line_1-1-voicemail">
(The checking and un-checking of the checkboxes update the backend)
Based on this thread, Django - Show BooleanField in a formset as one group of radio buttons, I modified the init and add_prefix method of my phoneline form to now display the voicemail field as radio buttons..
class PhoneLineForm(ModelForm):
def __init__ (self, *args, **kwargs):
super(PhoneLineForm, self).__init__(*args, **kwargs)
self.fields['voicemail'] = BooleanField( widget = RadioSelect(choices=((self.prefix+"-"+str("voicemail"), 'Voicemail LIne'),)))
def add_prefix(self, field):
if field == 'voicemail': return ('%s_%s') %(field, self.instance.phone.id)
else: return self.prefix and ('%s-%s' % (self.prefix, field)) or field
This modifies the html output to the following:
form1 of the formset
<input type="radio" id="id_voicemail_1_0" value="phone_client_line_1-0-voicemail" name="voicemail_1">
form2 of the formset
<input type="radio" id="id_voicemail_1_0" value="phone_client_line_1-1-voicemail" name="voicemail_1">
As you see, now for the radio field voicemail_1, I have the value of the selected phoneline.
If I submit this form, after I select a voicemail_1 radio, it does not update the database.
How can I capture the value of the phoneline selected on submit?. If I overwrite the save method of the phonelineform, what should it contatin..?
Any pointers on this would be appreciated. Thanks!!
I'd actually suggest simplifying this, and relying on JavaScript (you can always validate server-side and show a message to the user if they have selected more than one phone line with voicemail because they've not got JavaScript enabled).
This would be simpler than trying to hack the field names and values. Django formsets are hard enough beasts to wrangle at the best of times, so adding further server-side complexity is often not worth the hassle, especially when the problem can be solved - in a not too-inelegant way - via a little judicious jQuery work.
$('input[name^="voicemail_"]').on('click',
function() {
$('input[name^="voicemail_"]').not($(this)).prop('checked', false);
}
);
Just a rough-and-ready suggestion; hope it's useful.

Conditional field in form

I need to make a Form class that may or not have a ReCaptcha field depending on whether the user is logged in or not.
Because this is a CommentForm, I have no access to the request object on form creation/definition, so I can't rely on that.
For the POST request the solution is easy: I've got this:
class ReCaptchaCommentForm(CommentForm):
def __init__(self, data=None, *args, **kwargs):
super(ReCaptchaCommentForm, self).__init__(data, *args, **kwargs)
if data and 'recaptcha_challenge_field' in data:
self.fields['captcha'] = ReCaptchaField()
Having done this, form validation should work as intended. The problem now is on the template side. I need the template to be like this:
<form action={% comment_form_target %} method="post">
{# usual form stuff #}
{% if not user.is_authenticated %}
<script type="text/javascript"
src="http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
<div id="recaptcha-div"></div>
<script type="text/javascript">
Recaptcha.create({{ public_key }}, "recaptcha-div",
{ theme: 'white',
callback: Recaptcha.focus_response_field });
</script>
{% endif %}
</form>
But I'd like not to have to repeat that code on every comments/*/form.html template. I gather there should be some way of adding equivalent code from a widget's render method and Media definition.
Can anyone think of a nice way to do this?
I assume that you instatiate your form in a view, so you could just pass the user from request to the form (just like in auth app SetPassword form):
def __init__(self, user, data=None, *args, **kwargs):
super(ReCaptchaCommentForm, self).__init__(data, *args, **kwargs)
if user.is_authenticated():
self.fields['captcha'] = ReCaptchaField()
Use crispy-forms!
You can include html elements in the form layout that would allow you to exclude/include a field based on the views request context. Extremely useful features outside of that as well.
Here's the relevant doc section.
What I am doing about conditional fields is having a base class (which inherits from Form) and other subclasses with the extra conditional fields.
Then in my view, depending on the condition I choose the required subclassed form. I know that it involves some duplicated code, but it seems easier than other approaches.
Well, it's unfortunate that django-floppyforms doesn't give access to the request. It would have been nice to know it was an option, as I've recently started using django-floppyforms in my own project.
Short of that, the best thing I can think of is to simply rely on template inheritance. You can create a comments/form.html file and then have each comments/*/form.html extend that. Put the Recaptcha code as you have it in the base form.html and you're good to go.

Django Admin - saving data from custom form

I need to customize the admin panel added to the model page related inputs from another model​​. But I can not figure out how to save them.
admin.py
class OrderAdmin(admin.ModelAdmin):
change_form_template = 'admin/testapp/order/change_form.html'
def change_view(self, request, object_id, extra_context=None):
order = Order.objects.get(id=object_id)
card_list = Card.objects.all().filter(type=order.type)
result = super(OrderAdmin, self).change_view(request, object_id, extra_context={
'card_list': card_list,
})
return result
change_form.html
{% for card in card_list %}
<input type="text" name="card-{{ card.id}}" value="{{ card.qty }}"></td>
{% endfor %}
How to save the changed values ​​in the Card model?
I tried to do as described here:
https://docs.djangoproject.com/en/1.3/ref/contrib/admin/#adding-custom-validation-to-the-admin
But self.cleaned_data does not include my data from inputs.
Thanks.
UPD: Well, I caught the data, but I think it's pretty messy way.
I'm hardly can imagine how I would calculate the id from inputs.
def save_model(self, request, obj, form, change):
request.POST['card-288']
Saving should be done in save_model. Your card_list data should also be would have been available in the form parameter if you had used django forms. Although you could still access it through request as you correctly pointed out.
You will also have to sanitize the data yourself, something that django forms does for your automatically.
You are getting exactly what you produced :), nothing unexpected there!
You must override clean (take a look here for instance) if you want some fields that you manually added in the change_list to be taken care of.
If you go that way, to retrieve the ID is up to you, but a simple split('-')[-1] would work.
This answers your specific need.
On the other side, I think your are not going the right way. The django admin is good enough to deal with ForeignKeys ManyToMany, so I see no need to do things yourself.
While you can get it working with the above hint, I suggest you start all over - if you want, a new SO question where you publish the initial Model and what exactly you are trying to achieve.