How to hide model field in Django Admin? - django

I generate field automaticly, so I want to hide it from user. I've tried editable = False and hide it from exclude = ('field',). All this things hide this field from me, but made it empty so I've got error: null value in column "date" violates not-null constraint.
models.py:
class Message(models.Model):
title = models.CharField(max_length = 100)
text = models.TextField()
date = models.DateTimeField(editable=False)
user = models.ForeignKey(User, null = True, blank = True)
main_category = models.ForeignKey(MainCategory)
sub_category = models.ForeignKey(SubCategory)
groups = models.ManyToManyField(Group)`
admin.py:
class MessageAdminForm(forms.ModelForm):
def __init__(self, *arg, **kwargs):
super(MessageAdminForm, self).__init__(*arg, **kwargs)
self.initial['date'] = datetime.now()
class MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
list_display = ('title','user',)
list_filter = ('date',)

Based on your model setup, I think the easiest thing to do would change your date field to:
date = models.DateTimeField(auto_now=True)
that should accomplish what you're after and you don't even need to exclude it from the admin, it's excluded by default. If you have auto_now=True it will act as a 'last update time'. If you have auto_now_add=True it will act as a creation time stamp.
There are several other ways you could accomplish your goal if your use case is more complex than a simple auto date field.
Override the model's save method to put the value in.
class Message(models.Model):
title=models.CharField(max_length=100)
date = models.DateTimeField(editable=False)
def save(*args, **kwargs):
self.date = datetime.datetime.now()
super(Message, self).save(*args, **kwargs)
What you are trying to do with the Model Admin isn't quite working because by default django only transfers the form fields back to a model instance if the fields are included. I think this might be so the model form doesn't try to assign arbitrary attributes to the model. The correct way to accomplish this would be to set the value on the instance in your form's save method.
class MessageAdminForm(forms.ModelForm):
def save(*args, **kwargs):
self.instance.date = datetime.now()
return super(MessageAdminForm, self).save(*args, **kwargs)

Related

Django model form with field not existing in model

I am using Django 3.2
I have a model like this:
class BannedUser(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="ban_info")
reason = models.PositiveSmallIntegerField(choices=BANN_REASON_CHOICES)
banned_at = models.DateTimeField(auto_now_add=True)
expiry_date = models.DateField(null=True, blank=True, help_text=_('Date on which ban expires'))
I want to create a form that instead of asking user to select a date, simply asks the user to select the Ban duration. The form will then calculate the expiration_date in the clean() method.
BAN_DURATION_3_DAYS=3
# ...
BAN_DURATION_CHOICES = (
(BAN_DURATION_3_DAYS, _('3 Days')),
# ...
)
class BannedUserForm(forms.ModelForm):
class Meta:
model = BannedUser
fields = ['reason', 'ban_till']
The form field ban_till is a PositiveInteger that maps to the number of days. The intention is then to calculate the expiry_date from today by offsetting the integer amount.
I suppose one way would be to:
create a dynamic field ban_till
add field expiry_date to the form field list (but somehow prevent it from being rendered)
in the form's clean() method calculate the expiry_date and update that field
How to create a form to display field that does not exist in Model?
My solution:
class BannedUserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['ban_till'] = forms.IntegerField(widget=forms.ChoiceField())
super().__init__(*args, **kwargs)
class Meta:
model = BannedUser
fields = ['reason']
Is this the correct way to do this - or are there any gotchas I need to be aware of?

Update a model field in save method based on other field values selected including m2m field

I am trying to update a model field by overriding save method:
class DataTable(models.Model):
id = models.AutoField(db_column='Id', primary_key=True)
country= models.ForeignKey(Countries,on_delete=models.DO_NOTHING,db_column='CountryId')
university=models.ManyToManyField(Universities,db_column='UniversityId',verbose_name='University',related_name='universities')
intake = models.CharField(db_column='Intake',blank=True, null=True, max_length=20, verbose_name='Intake')
year = models.CharField(max_length=5,blank=True,null=True)
application_status = models.ForeignKey(Applicationstages,on_delete=models.DO_NOTHING, db_column='ApplicationStageId',verbose_name='Application Status')
requested_count = models.PositiveIntegerField(db_column='RequestedCount',blank=True,null=True)
class Meta:
db_table = 'DataTable'
def __str__(self):
return str(self.application_status)
def __unicode__(self):
return str(self.application_status)
def save(self,*args, **kwargs):
super(DataTable,self).save(*args, **kwargs)
country_id= self.country
intake= self.intake
year= self.year
universities= self.university.all()
courses = get_ack_courses(country_id,universities)
all_data = get_all_courses(intake,year,courses)
ack_data = get_acknowledgements_data(all_data)
self.requested_count =len(ack_data)
super(DataTable,self).save(*args, **kwargs)
I am trying to update the requested_count field using the other field values, but in save method when I try to get the m2m field data ; it is returning empty. I tried with post_save signal also and there also m2m field data is empty.
I want the count field based on otherfields from this model. How to save when we have m2m fields?

django manytomany model relationship crashing admin on object create

I have an Event object in my postgres db, and created a new Collection object to group events by theme via a ManyToMany field relationship:
class Collection(models.Model):
event = models.ManyToManyField('Event', related_name='collections')
name = models.CharField(blank=True, max_length=280)
slug = AutoSlugField(populate_from='name')
image = models.ImageField(upload_to='collection_images/', blank=True)
description = models.TextField(blank=True, max_length=1000)
theme = models.ManyToManyField('common.Tag', related_name='themes')
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=False)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('collection', args=[self.slug])
def clean(self):
# because of the way db saves M2M relations, collection doesn't have a
# type at this time yet, so image inheritance is
# called from the signal which is triggered when M2M is created
# (that means if an image is later deleted, it won't inherit a new
# one when collection is saved)
if self.image:
validate_hero_image(self.image, 'image')
def save(self, *args, **kwargs):
try:
self.full_clean()
except ValidationError as e:
log.error('Collection validation error (name = %s): %s' % (self.name, e))
return super(Collection, self).save(*args, **kwargs)
in my admin, I'm defining and registering CollectionAdmin like this:
class CollectionAdmin(admin.ModelAdmin):
model = Collection
verbose_name = 'Collection'
list_display = ( 'name', )
however, if I go into admin and attempt to create a Collection "GET /admin/app/collection/add/" 200, the request frequently times out and the query load on my database from the Event M2M relationship seems quite heavy from logging. For reference currently the db has ~100,000 events. are there better ways to (re)structure my admin fields so I can select specific events (by name or id) to add to a Collection without effectively requesting a QuerySet of all events when that view is loaded (or creating them in db via shell)? thanks
There are multiple ways to accomplish this. For example, you could override the form fields the admin uses and specify another widget to use, like NumberInput.
You could also add your event model field to the raw_id_fields attribute of ModelAdmin. By doing so, Django won't try to create a fully populated select input but will offer you a way to search for events manually if needed:
class CollectionAdmin(admin.ModelAdmin):
model = Collection
verbose_name = 'Collection'
list_display = ('name', )
raw_id_fields = ('event', )

Django: why the manytomany choice box only has on side

I have extended the group model, where I added some manytomany fields, and in the admin page, it likes this:
However, what I expected is this:
Here is how I implemented the m2m field:
class MyGroup(ProfileGroup):
mobile = models.CharField(max_length = 15)
email = models.CharField(max_length = 15)
c_annotates = models.ManyToManyField(Annotation, verbose_name=_('annotation'), blank=True, null=True)
c_locations = models.ManyToManyField(Location, verbose_name=_('locations'), blank=True, null=True)
And in the database there is a relational form which contains the pairs of group_id and location_id.
Is there anyone who knows how to do it? Thanks!
EDIT:
I implemented as above, the multiple select box actually shows up, but it cannot save... (Sorry, I was working on a virtual machine and it's offline now, so I have to clip the code from screen)
latest 2017
govt_job_post is model having qualification as ManyToMany field.
class gjobs(admin.ModelAdmin):
filter_horizontal = ('qualification',)
admin.site.register(govt_job_post, gjobs)
Problem solved. It can save the multiple choice field now.
class GroupAdminForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(queryset=User.objects.all(),
widget=FilteredSelectMultiple('Users', False),
required=False)
locations = forms.ModelMultipleChoiceField(queryset=Location.objects.all(),
widget=FilteredSelectMultiple('Location', False),
required=False)
class Meta:
model = Group
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', None)
if instance is not None:
initial = kwargs.get('initial', {})
initial['users'] = instance.user_set.all()
initial['locations'] = instance.c_locations.all()
kwargs['initial'] = initial
super(GroupAdminForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
group = super(GroupAdminForm, self).save(commit=commit)
if commit:
group.user_set = self.cleaned_data['users']
group.locations = self.cleaned_data['locations']
else:
old_save_m2m = self.save_m2m
def new_save_m2m():
old_save_m2m()
group.user_set = self.cleaned_data['users']
group.location_set = self.cleaned_data['locations']
self.save_m2m = new_save_m2m
return group
Either I am overlooking something that makes your situation unusual or you are making it harder than it needs to be. Since you're using the admin, the vast majority of the code necessary to use the admin's more convenient multiselects is already available. All you should need to do is declare your ManyToMany fields, as you have, and then include those fields in your admin class's filter_horizontal attribute. Or filter_vertical if you want the boxes stacked, but your screenshot shows the horizontal case.
This by itself does not require a custom form for your admin.

Django CBV Forms prepopulated foreign key dataset

I'm trying to use CBVs as much as possible and want to pre-populate data in a ModelForm based on a generic.CreateView with some data passed in via URL.
I might be over thinking or confusing myself. All code abridged for legibility
We have an inventory system with PartNumbers (abstractions), Carriers (actual instances of PartNumbers with location, serial and quantity numbers) and Movements for recording when items are extracted from the inventory, how much is taken and what Carrier it came from.
I would like to have the "extract inventory" link on the PartNumber detail page, and then have the available carriers ( pn.carrier_set.all() ) auto filled into the FK drop down on the MovementForm.
models.py
class PartNumber(models.Model):
name = models.CharField("Description", max_length=100)
supplier_part_number = models.CharField(max_length=30, unique=True)
slug = models.SlugField(max_length=40, unique=True)
class Carrier(models.Model):
part_numbers = models.ForeignKey(PartNumber)
slug = models.SlugField(max_length=10, unique=True, blank=True, editable=False)
location = models.ForeignKey(Location)
serial_number = models.CharField(max_length=45, unique=True, null=True, blank=True)
qty_at_new = models.IntegerField()
qty_current = models.IntegerField()
class Movement(models.Model):
carrier = models.ForeignKey(Carrier)
date = models.DateField(default=timezone.now())
qty = models.IntegerField()
I have been playing around with get_initial() and get_form_kwargs() without success:
In urls.py I collect the PartNumber via url as pn_slug
url(r'^partnumber/(?P<pn_slug>[-\w]+)/extract/$', views.MovementCreate.as_view(), name='pn_extract'),
forms.py is generic
class MovementForm(forms.ModelForm):
class Meta:
model = Movement
views.py
class MovementCreate(generic.CreateView):
form_class = MovementForm
model = Movement
def get_form_kwargs(self):
kwargs = super(MovementCreate, self).get_form_kwargs()
kwargs['pn_slug'] = self.request.POST.get("pn_slug")
return kwargs
# here we get the appropriate part and carrier and.
# return it in the form
def get_initial(self):
initial = super(MovementCreate, self).get_initial()
# this didn't work, hence using get_form_kwargs
#pn = PartNumber.objects.get(slug=self.request.POST.get("pn_slug"))
pn = PartNumber.objects.get(slug=self[pn_slug])
carriers = pn.carrier_set.all()
initial['carrier'] = carriers
return initial
As it stands, I'm getting "global name 'pn_slug' is not defined" errors - but I doubt that error accurately reflects what I have done wrong.
I have been using these posts as rough guidelines:
How to subclass django's generic CreateView with initial data?
How do I use CreateView with a ModelForm
If I understand you correctly from our comments, all you need is just to change the queryset of the MovementForm's carrier field to set the available options. In that case, I would use get_initial nor get_form_kwargs at all. Instead, I would do it in get_form:
def get_form(self, *args, **kwargs):
form = super(MovementCreate, self).get_form(*args, **kwargs)
pn = PartNumber.objects.get(slug=self.kwargs['pn_slug'])
carriers = pn.carrier_set.all()
form.fields['carrier'].queryset = carriers
return form
Another way to do it would be to use get_form_kwargs:
def get_form_kwargs(self):
kwargs = super(MovementCreate, self).get_form_kwargs()
kwargs['pn_slug'] = self.kwargs.get("pn_slug")
return kwargs
Then, in the form's __init__, set the queryset:
class MovementForm(forms.ModelForm):
class Meta:
model = Movement
def __init__(self, *args, **kwargs):
pn_slug = kwargs.pop('pn_slug')
super(MovementForm, self).__init__(*args, **kwargs)
pn = PartNumber.objects.get(slug=pn_slug)
carriers = pn.carrier_set.all()
self.fields['carrier'].queryset = carriers
Personally, I would prefer the first method as it is less code.