Reset field in Django Admin page - python

Imagine, that I have model and in the Django admin form I can update my fields. I want to implement something like: update one field and the second one will be reset in admin form in real time (I'll hope it can help real admin in the future do not forget redefine this second field, because it's very important in my situation). Is that possible to implement something like that without custom admin form?

To achieve this without a custom template or form, you can just include a custom script, for example:
Assuming you have an app named people and it has a model called Person with two fields first_name and last_name:
Include javascript into the admin page
# people/admin.py
from django.contrib import admin
from people.models import Person
class PersonAdmin(admin.ModelAdmin):
class Media:
js = ('people/js/person.js',)
admin.site.register(Person, PersonAdmin)
Then create the person/js/person.js script:
'use strict';
//execute when everything is loaded
window.addEventListener('load', () => {
let first_field = document.querySelector('form input[name=first_name]'); //field name
let second_field = document.querySelector('form input[name=last_name]'); //field name
//just when the fields are found (add or update)
if(first_field && second_field) {
//here you can implement whatever logic you want
first_field.addEventListener('input', () => {
second_field.value = '';
});
}
});
Now every time the first_name changes last_name will be cleared. This idea can be extended to do more interesting things.

Related

Create a confirmation message from model in Django

I have an app in Django that has multiple models. I have a particular model like this:
models.py
class MyModel(models.Model):
model_id= models.AutoField(primary_key=True)
model_date = models.DateTimeField(verbose_name="Label 1")
model_counter = models.IntegerField(blank=True, null=True)
admin.py
class MyModelAdmin(admin.ModelAdmin):
list_display = ('model_id', 'model_date ', 'model_counter ')
list_filter = (
('model_date', DropdownFilter)
)
def has_delete_permission(self, request, obj=None):
return False
def get_actions(self, request):
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
readonly_fields = ['model_counter ']
admin.site.register(MyModel, MyModelAdmin)
What I need is that the user confirm that he wants to save the model, even if the date is greater than today, in doing so the user can create a model that can be wrong for other users, but sometime it's correct that the date is greater than today.
I cannot use a custom HTML form, only the default one.
The user must interact someway with the page, and give direct aknowledgement that he knows that is saving a new model that can be dangerous for other users. The user must be able to abort the saving or modify the date.
So I tried to use another field of the model to store the counter:
def clean(self):
condition = False
if self.model_counter is None:
condition = True
else:
condition = self.model_counter == 1
if condition :
self.model_counter = 1
raise ValidationError("Attention, the date inserted is after the current date, click the SAVE button another time to proceed with the saving")
As a counter I use another field of the same model. I'm not able to make the updating of the counter working. From what I have understood, the lifecycle of validation prevent me to alter in the proper way the state of the istance of the model that the code it's saving, so the updating of the field model it's ignored.
There are is any way to achieve my goal? I used the model field for storing the value of counter because I'm not able in another way. I don't care where is the counter. I don't care also to use the message system of Django or a popup. I need only to force the user under some condition to make an interaction that force him to continue or abort the saving.
Edit
I added also the code in the admin.py for more clearness. I modified only the models and the admin, invoke the command: python3 manage.py inspectdb > models.py and I got all the code generated. That it's the standard procedure for this things in my company. So I cannot add (or I don't how) code to the Web pages generated from Django.
I think you would be best to use some JavaScript here. Where you add a click event to the submit button where a modal/dialag asks the user to confirm. If they say Yes, then you can submit the form.
For example with dialog (you can make it modal if you want):
HTML
<form id="myModelForm" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button id="myModelSave">{% trans "Save" %}</button>
</form>
JS
let saveButton = document.getElementById('myModelSave');
saveButton.onclick = function() {
let accepted = confirm("Are you sure you want to save?");
if (accepted) {
document.getElementById('myModelForm').submit();
}
}

Django Rest Framework return True if relation exist

I have two questions. How i can just return true if relation exist? For example I have post model and comment model also, comment have foreginKey to post. Now after post serialization i wanna have something like this
{
id: 2
content: "My first post!"
has-comments: True
}
And my second question is how rename field name in model relation? Again we have post and comment. In comment model i have foregin key to post like
post = models.ForeignKey(Post)
Now when i add new comment i send JSON data with {post: postIdHere}. Is possible to change post to postId only in drf not in django model?
I hope you understand me :)
Best Redgards,
Sierran.
The closest thing I can come up with is a custom has_comments field (rather than has-comments) with this in the serializer:
from rest_framework import serializers
class YourSerializer(Either Serializer or ModelSerializer...):
has_comments = serializers.SerializerMethodField()
#staticmethod
def get_has_comments(instance):
# Choose whichever one works for you.
# You did not specify some model names, so I am just making stuff up.
return instance.post_set.exists()
return Comment.objects.filter(post_id=instance.id).exists()
You may also have to specify the field in the serializer's Meta class. When first run, the framework will tell you exactly how.

Suit form include on list display in admin

Im trying to use Django Suit's form includes to add a button to the list display of all my subscribers in the admin. In the documentation it says to add this to your admin.py file in the right app.
class SubscriberAdmin(admin.ModelAdmin):
list_display = ('email', 'date')
readonly_fields = ('email', 'date')
def has_add_permission(self, request):
return False
suit_form_includes = (
('admin/suit_includes/suit_csv.html', 'top'),
)
However this only appears when clicking into an instance of an object, and doesn't show up on the admin page that shows the entire list of objects in the database. Is there a way to do this with Django Suit ? I had trouble finding anything on google.
suit form include template:
<button class="btn btn-info">Export to File</button>
Admin instance display (Where its appearing):
Admin list display (Where I want it to appear):
What's doing django-suit, here, is that it is including your HTML snippet in the change_form that is displayed for your model, the change_form being where you can modify your model and save the changes into the database.
Where you want it to appear is the "change_list", aka the place where you can see all of your instances of that model in your database.
To add it your html snippet, you should extend your change_list.html with your own snippet : More info on expanding templates in the official documentation
Good luck !

Django admin. Hide field on change select field

I have model:
class CategoryField(models.Model):
selectfield = models.IntegerField(choices=SELECT_FIELD_CHOICES, default=1)
verified = models.BooleanField(default=True, verbose_name='Required?')
In admin page I have selectField with choices: "value1", "value2", "value3", ...
When I select "value2" I need to show verified field. How can I do it?
You would need to add some JavaScript in order to show or hide the field. jQuery 1.9.1 is available in Django admin already via the django.jQuery object.
The simplest way to add this JavaScript is to add a Media meta class to your model form and add the form to the ModelAdmin:
# forms.py
from django import forms
class CategoryFieldForm(forms.ModelForm):
. . .
class Media:
js = ('category-field-admin.js',)
# admin.py
from django.contrib import admin
from your_app.forms import CategoryFieldForm
from your_app.models import CategoryField
class CategoryFieldAdmin(admin.ModelAdmin):
form = CategoryFieldForm
admin.site.register(CategoryField, CategoryFieldAdmin)
# category-field-admin.js
// pseudo code - change as needed
(function($) {
$(function() {
var selectField = $('#id_selectField'),
verified = $('#id_verified');
function toggleVerified(value) {
value == 'value2' ? verified.show() : verified.hide();
}
// show/hide on load based on pervious value of selectField
toggleVerified(selectField.val());
// show/hide on change
selectField.change(function() {
toggleVerified($(this).val());
});
});
})(django.jQuery);

Django-Registration & Django-Profile, using your own custom form

I am making use of django-registration and django-profile to handle registration and profiles. I would like to create a profile for the user at the time of registration. I have created a custom registration form, and added that to the urls.py using the tutorial on:
http://dewful.com/?p=70
The basic idea in the tutorial is to override the default registration form to create the profile at the same time.
forms.py - In my profiles app
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from profiles.models import UserProfile
from registration.models import RegistrationProfile
attrs_dict = { 'class': 'required' }
class UserRegistrationForm(RegistrationForm):
city = forms.CharField(widget=forms.TextInput(attrs=attrs_dict))
def save(self, profile_callback=None):
new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'])
new_profile = UserProfile(user=new_user, city=self.cleaned_data['city'])
new_profile.save()
return new_user
In urls.py
from profiles.forms import UserRegistrationForm
and
url(r'^register/$',
register,
{'backend': 'registration.backends.default.DefaultBackend', 'form_class' : UserRegistrationForm},
name='registration_register'),
The form is displayed, and i can enter in City, however it does not save or create the entry in the DB.
You're halfway there - you've successfully built a custom form that replaces the default form. But you're attempting to do your custom processing with a save() method on your model form. That was possible in older versions of django-registration, but I can see from the fact that you specified a backend in your URL conf that you're using v0.8.
The upgrade guide says:
Previously, the form used to collect
data during registration was expected
to implement a save() method which
would create the new user account.
This is no longer the case; creating
the account is handled by the backend,
and so any custom logic should be
moved into a custom backend, or by
connecting listeners to the signals
sent during the registration process.
In other words, the save() method on the form is being ignored now that you're on version 0.8. You need to do your custom processing either with a custom backend or with a signal. I chose to create a custom back-end (if anyone has gotten this working with signals, please post code - I wasn't able to get it working that way). You should be able to modify this to save to your custom profile.
Create a regbackend.py in your app.
Copy the register() method from DefaultBackend into it.
At the end of the method, do a query to get the corresponding User instance.
Save the additional form fields into that instance.
Modify the URL conf so that it points to BOTH the custom form AND the custom back-end
So the URL conf is:
url(r'^accounts/register/$',
register,
{'backend': 'accounts.regbackend.RegBackend','form_class':MM_RegistrationForm},
name='registration_register'
),
regbackend.py has the necessary imports and is basically a copy of DefaultBackend with just the register() method, and the addition of:
u = User.objects.get(username=new_user.username)
u.first_name = kwargs['first_name']
u.last_name = kwargs['last_name']
u.save()
As described in my comment on Django Trac ticket I made a metaclass and mixin to allow multiple inheritance for ModelForm Django forms. With this you can simply make a form which allows registration with fields from user and profile models at the same time without hard-coding fields or repeating yourself. By using my metaclass and mixin (and also fieldset mixin) you can do:
class UserRegistrationForm(metaforms.FieldsetFormMixin, metaforms.ParentsIncludedModelFormMixin, UserCreationForm, UserProfileChangeForm):
error_css_class = 'error'
required_css_class = 'required'
fieldset = UserCreationForm.fieldset + [(
utils_text.capfirst(UserProfileChangeForm.Meta.model._meta.verbose_name), {
'fields': UserProfileChangeForm.base_fields.keys(),
})]
def save(self, commit=True):
# We disable save method as registration backend module should take care of user and user
# profile objects creation and we do not use this form for changing data
assert False
return None
__metaclass__ = metaforms.ParentsIncludedModelFormMetaclass
Where UserCreationForm can be for example django.contrib.auth.forms.UserCreationForm form and UserProfileChangeForm a simple ModelForm for your profile model. (Do not forget to set editable to False in your foreign key to User model.)
With django-registration backend having such register method:
def register(self, request, **kwargs):
user = super(ProfileBackend, self).register(request, **kwargs)
profile, created = utils.get_profile_model().objects.get_or_create(user=user)
# lambda-object to the rescue
form = lambda: None
form.cleaned_data = kwargs
# First name, last name and e-mail address are stored in user object
forms_models.construct_instance(form, user)
user.save()
# Other fields are stored in user profile object
forms_models.construct_instance(form, profile)
profile.save()
return user
Be careful that registration signal is send at the beginning of this method (in method in superclass) and not at the end.
In the same manner you can make a change form for both user and profile information. Example for this you can find in my comment on Django Trac ticket mentioned above.
With registration 0.8 and later:
Create a subclass of registration.backends.default.views.RegistrationView in your views.py or equivalent:
from registration.backends.default.views import RegistrationView
class MyRegistrationView(RegistrationView):
form_class= MyCustomRegistrationForm
def register(self, request, **cleaned_data):
new_user= super(MyRegistrationView, self).register(request, **cleaned_data)
# here create your new UserProfile object
return new_user

Categories