Django don't save ModelMultipleChoiceField in admin - django

I want to filter skill_ids fields and create 3 "abstract" fields for every SkillType, but now it's not saving this hard_skills field in admin site.
Model
class Task(models.Model):
name = models.CharField(max_length=255)
category_id = models.ForeignKey('Category', on_delete=models.RESTRICT, null=True)
level_id = models.ForeignKey('Level', on_delete=models.RESTRICT, null=True)
permission_ids = models.ManyToManyField('Permission', blank=True)
skill_ids = models.ManyToManyField('Skill', blank=True)
Form
class TaskForm(ModelForm):
hard_skills = ModelMultipleChoiceField(Skill.objects.filter(skill_type=SkillType.HARD_SKILL),
widget=FilteredSelectMultiple("Hard Skills", False), required=False)
class Meta:
model = Task
exclude = ['skill_ids']
Admin
#admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_per_page = 25
list_display = ['name', 'category_id', 'level_id', 'get_permissions']
list_filter = ['category_id']
filter_horizontal = ['permission_ids', 'skill_ids']
form = TaskForm
def save_model(self, request, obj, form, change):
for hard_skill in form.cleaned_data.get('hard_skills'):
obj.skill_ids.set(hard_skill)
super().save_model(request, obj, form, change)

ManyToManyField method set requires a list, so this should do:
obj.skill_ids.set(form.cleaned_data.get('hard_skills'))

Related

How to change a field of model prior saving the model based on another field in django 3.1

I need to be able to set the KeyIndex field of the Settings model to a value that is equal to
lastExtension - firstExtension
How can i do that
this is the content of my model
models.py
class Settings(models.Model):
KeyIndex = models.CharField(max_length=150, blank=True, name='Key_Index')
firstExtension = models.CharField(max_length=15, blank=False, null=False, default='1000')
lastExtension = models.CharField(max_length=15, blank=False, null=False, default='1010')
def save(self, *args, **kwargs):
f = int(self.firstExtension)
l = int(self.lastExtension)
a = [0] * (l - f)
self.KeyIndex = str(a)
return super(Settings, self).save()
class KeyFiles(models.Model):
setting = models.ForeignKey(Settings, on_delete=models.CASCADE)
keyFile = models.FileField(upload_to='key File', null=True, blank=True, storage=CleanFileNameStorage,
validators=[FileExtensionValidator(allowed_extensions=['bin']), ])
this is the content of my form
forms.py
class ShowAdminForm(forms.ModelForm):
class Meta:
model = Settings
fields = '__all__'
files = forms.FileField(
widget=forms.ClearableFileInput(attrs={"multiple": True}),
label=_("Add key Files"),
required=False,validators=[FileExtensionValidator(allowed_extensions=['bin'])]
)
def save_keyFile(self, setting):
file = KeyFiles(setting=setting, keyFile=upload)
file.save()
and t
admin.py
class KeyFilesAdmin(admin.TabularInline):
model = KeyFiles
#admin.register(IPPhone_Settings)
class IPPhoneSettingsAdmin(admin.ModelAdmin):
form = ShowAdminForm
inlines = [KeyFilesAdmin]
def save_related(self, request, form, formsets, change):
super(IPPhoneSettingsAdmin, self).save_related(request, form, formsets, change)

Add extra value before save serializer

My form sends data to django-rest-framework, but the form contains two fields, and I want to save 5 fields in the database, other fields I calculate on my own (they are not sent by the form). How can I add additional values before saving?
so, form send 'user' and 'comment' values, I want add 'article', 'ip_address' before save to DB
models.py
class Comments(models.Model):
article = models.ForeignKey(Articles, on_delete=models.CASCADE)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
comment = models.TextField(verbose_name=_('Comment'))
submit_date = models.DateTimeField(_('Created'), auto_now_add=True)
ip_address = models.CharField(_('IP address'), max_length=50)
is_public = models.BooleanField(verbose_name=_('Publish'), default=False)
serializers.py
class CommentsSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.first_name')
class Meta:
model = Comments
fields = ('user', 'comment')
views.py
class AddCommentViewSet(viewsets.ModelViewSet):
queryset = Comments.objects.all()
serializer_class = CommentsSerializer
You have to override create() method:
class CommentsSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.first_name')
class Meta:
model = Comments
fields = ('user', 'comment')
def create(self, validated_data):
new_comment = models.Comment()
new_comment.user = validated_data['user']
new_comment.comment = validated_data['comment']
new_comment.article = get_your_article_somehow()
new_comment.ip_address = get_your_ip_address_somehow()
new_comment.save()
return new_comment

setting instance for forms in UpdateView

i have an UpdateView with a couple of forms and i'm trying to understand how to set the instance for the other form because the first form work just fine but the second form is always empty and i cant figure out how to set the instance for that modelform .
class ProfileUpdateView(UpdateView):
# model = User
queryset = User.objects.all()
form_class = UserForm
second_form_class = ClientForm
template_name = 'accounts/update.html'
def get_object(self):
user = get_object_or_404(User , username__iexact=self.kwargs.get('username'))
return user
def get_context_data(self, **kwargs):
user = self.object
profile = Client.objects.get(id = user.clients.id)
context = super(ProfileUpdateView, self).get_context_data(**kwargs)
if user.is_client and 'ClientForm' not in context:
context['client_form'] = self.second_form_class(self.request.GET, instance=profile )
return context
models
class User(AbstractUser):
gender_choice =(
('Male','Male'),
('Female','Female'),
)
is_artisan = models.BooleanField('artisan status', default=False)
is_client = models.BooleanField('client status', default=False)
avatar = models.ImageField(null=True ,blank=True)
birth_day = models.DateField(null=True,blank=True)
birth_location = models.CharField(max_length=30, null=True ,blank=True)
adresse = models.CharField(max_length=30, null=True ,blank=True)
gender = models.CharField(max_length=6,choices=gender_choice,)
phone = models.CharField(max_length=10 ,null=True ,blank=True)
class Client(models.Model):
client_choice = (
('erson','person'),
('company','company'),
)
client_type = models.CharField(max_length=10,choices=client_choice,)
user = models.OneToOneField(settings.AUTH_USER_MODEL,on_delete=models.CASCADE, related_name='clients')
forms
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email','avatar','adresse','birth_location','birth_day' ,'gender' ,'phone')
class ClientForm(forms.ModelForm):
class Meta:
model = Client
fields = ('client_type',)
the question now how/where can i set the instance for the second form and where is the first form instance is set .
Is that a typo in get_context_data? Should it be:
if user.is_client and 'client_form' not in context:
context['client_form'] = self.second_form_class(self.request.GET, instance=profile)
The first form is set in UpdateView's super class FormMixin.get_context_data (which in turn calls FormMixin.get_form()). Docs for FormMixin

Show model values as multi select options in admin

I am trying to store use the values from two different models to show as choices in form and store them in new model.
Here is what I have now.
models.py
class Employee(models.Model):
name = models.CharField(max_length=200)
class Product(models.Model):
code = models.CharField(primary_key=True, max_length=5)
class JobQueue(models.Model):
emp_name = models.CharField(max_length=200)
product_code = models.CharField(max_length=5)
forms.py
class JobQueueForm(forms.ModelForm):
emp_choices = Employee._meta.get_field('name').choices
product_code_choices = Product._meta.get_field('code').choices
emp_name = forms.ChoiceField(choices = emp_choices)
product_code =forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=product_code_choices)
def save(self, commit=True):
return super(JobQueueForm, self).save(commit = commit)
class Meta:
model = JobQueue
fields = ('emp_name', 'product_code')
admin.py
class JobQueueAdmin(admin.ModelAdmin):
form = JobQueueForm
fieldsets = (
(None,{
'fields': ('emp_name', 'product_code'),}),
)
def save_model(self, request, obj, form, change):
super(JobQueueAdmin, self).save_model(request, obj, form, change)
admin.site.register(models.Employee, AuthorAdmin)
admin.site.register(models.Product, ProductAdmin)
admin.site.register(models.JobQueue, JobQueueAdmin)
I do have values stored in Employee and Product models, but I dont see them as options in JobQueue model in Admin portal.
Am I missing anything here?
emp_choices = Employee._meta.get_field('name').choices
This line doesn't make sense. It tries to get the choices for the Employee.name field, but you did not specify any choices for the name field in your model.
class Employee(models.Model):
name = models.CharField(max_length=200)
If you want generate a list of choices from all the existing employees in the database, then you can define a method that does this:
def get_names():
return Employee.objects.values_list('name', 'name')
Then use this method in your form:
class JobQueueForm(forms.ModelForm):
emp_name = forms.ChoiceField(choices=get_names)
...

Django - Design choices, Override the model save() so it palys nice with contrib.admin?

I'm with some design issues in Django and getting all to play nice with contrib.admin.
My main problem is with Admin Inlines and the save_formset() method. I created a create() classmethod for the model but this do not play nice with save_formset(). I think Django admin have a way of doing this, not with a create() method. In the create() method in the AdPrice I basicaly want to update the field 'tags' in the model Ad.
My question: Instead of creating a create() classmethod it would be nice to override the model save() method so I don't have problems with contrib.admin?
My code:
Models:
class Ad(models.Model):
title = models.CharField(max_length=250)
name = models.CharField(max_length=100)
description = models.TextField()
tags = TaggableManager()
comment = models.TextField(null=True, blank=True)
user_inserted = models.ForeignKey(User, null=True, blank=True, related_name='user_inserted_ad')
date_inserted = models.DateTimeField(auto_now_add=True)
user_updated = models.ForeignKey(User, null=True, blank=True, related_name='user_updated_ad')
date_updated = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.name
class AdPrice(models.Model):
ad = models.ForeignKey(Ad)
name = models.CharField(max_length=50)
price = models.DecimalField(max_digits=6, decimal_places=2)
user_inserted = models.ForeignKey(User, null=True, blank=True, related_name='user_inserted_ad_price')
date_inserted = models.DateTimeField(auto_now_add=True)
user_updated = models.ForeignKey(User, null=True, blank=True, related_name='user_updated_ad_price')
date_updated = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.name
#classmethod
def create(cls, ad_id, name, price, date_inserted, user_inserted_id):
# Save price
new_register = AdPrice(ad_id=ad_id, name=name, price=price, date_inserted=date_inserted,
user_inserted=User.objects.get(id=user_inserted_id))
new_register.save()
# Add tags to Ad tags field
# AD SOME CODE HERE # To do
Admin:
class AdPriceInline(admin.TabularInline):
model = AdPrice
fieldsets = (
(None, {
'fields': ('name', 'price')
}),
)
class AdAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'title', 'telephone', 'comment',
'user_inserted', 'date_inserted', 'user_updated', 'date_updated')
fieldsets = (
(None, {
'fields': ('id', 'name', 'title', 'description', 'comment')
}),
)
inlines = (
AdPriceInline,
)
readonly_fields = ('id',)
def save_model(self, request, obj, form, change):
if change == False:
if getattr(obj, 'user_inserted', None) is None:
obj.user_inserted = request.user
super(AdAdmin, self).save_model(request, obj, form, change) # This line save the data on the Ad Model, now I have the pk to use bellow
obj.save()
# In the first insert, create a line in the AdHist model
# ad_status_id = 1 (Pending) | ad_change_reasons = 1(Insertion)
AdHist.create(ad_id=obj.id, datetime_begin=datetime.datetime.now(), datetime_end=None, ad_status_id=1, ad_change_reason_id=1,
user_inserted_id=request.user.id)
elif change == True:
if getattr(obj, 'user_updated', None) is None:
obj.user_updated = request.user
else:
obj.user_updated = request.user
if getattr(obj, 'date_updated', None) is None:
obj.date_updated = datetime.datetime.now()
else:
obj.date_updated = datetime.datetime.now()
obj.save()
def save_formset(self, request, form, formset, change):
if change == False:
instances = formset.save(commit=False)
for instance in instances:
if formset.model == AdPrice:
AdPrice.create(ad_id=instance.ad_id, name=instance.name, price=instance.price, date_inserted=datetime.datetime.now(),
user_inserted_id=request.user.id)
elif change == True:
for form in formset.forms:
None #form.instance.ad_id
formset.save()
A tricky part about overriding a model's save() method is that it doesn't get used in bulk operations, like bulk_create. Django recommends using a signal for a case like this (see the docs on how to set them up in your app).
Your signal might look something like this:
from django.db.models.signals import post_save
from .models import YourModel
def handle_model_post_save(sender, **kwargs):
# Ensure we are only running this code on create
if kwargs['created'] and kwargs['instance']:
# Your logic goes here
With this, the code in the signal will only run when a new instance of your model is created, not when existing instances are updated.