Django: 'readonly' attribute doesn't work on my ModelForm - python

There is a 'league_type' field in my form and I want to make it readonly. I used widget.attr['readonly'] = True but it doesn't work.
The model:
class League(models.Model):
league_types = (
('league', 'League'),
('knockout', 'Knockout'),
)
....
season = models.ForeignKey(Season, related_name = "league_season")
league_type = models.CharField(max_length=10, choices = league_types, default ='league')
...
The ModelForm:
class LeagueForm(forms.ModelForm):
class Meta:
model = League
fields = ('title', 'league_type', 'season', 'status')
widgets = {'season':forms.HiddenInput(),}
view.py:
if League.objects.filter(season = season, league_type = 'knockout').count():
form = LeagueForm(initial={'season': season, 'league_type': 'league'})
form.fields['league_type'].widget.attrs['readonly'] = True
else:
form = LeagueForm(initial={'season': season})
Update:
I cannot use disabled attribute because I'm going to create a form with league_type initial value.

Related

Django REST: ignoring custom fields which are not part of model

My TimeReport model looks like this:
class TimeReport(models.Model):
minutes_spent = models.PositiveIntegerField()
task = models.ForeignKey(Task, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
reported_for = models.DateField()
note = models.TextField(null = True, blank=True)
status = models.CharField(max_length=50, choices=State.choices, default=State.new)
user = models.ForeignKey(User, on_delete=models.PROTECT)
And my model serializer:
class TimeReportCreateSerializer(serializers.ModelSerializer):
class Meta:
model = TimeReport
fields = (
'id',
'minutes_spent',
'reported_for',
'note',
'status',
'task_custom_id',
)
task_custom_id = serializers.CharField()
def create(self, validated_data):
user = User.objects.get(auth_user_id = self.context['user_id'])
task = Task.objects.filter(custom_id = validated_data['task_custom_id']).filter(user = user.id).first()
report = TimeReport(**validated_data)
report.user = user
report.task = task
report.save()
return report
So, the problem is, that I want to take a custom value in a serializer, which is not a part of a model and do some custom logic with it - in this case search for the right 'task' in the database. But when I try to parse the model by using report = TimeReport(**validated_data), it gives me an exception:
TypeError at /api/report/
TimeReport() got an unexpected keyword argument 'task_custom_id'
Im kind of new to Django and python itself, so - what is the best approach?
If you are going to use that field only for creation, you should use write_only option.
task_custom_id = serializers.CharField(write_only=True)
See the docs here https://www.django-rest-framework.org/api-guide/fields/#write_only
You just need to remove task_custom_id from the dictionary
class TimeReportCreateSerializer(serializers.ModelSerializer):
class Meta:
model = TimeReport
fields = (
'id',
'minutes_spent',
'reported_for',
'note',
'status',
'task_custom_id',
)
task_custom_id = serializers.CharField()
def create(self, validated_data):
user = User.objects.get(auth_user_id = self.context['user_id'])
task_custom_id = validated_data.pop("task_custom_id")
task = Task.objects.filter(custom_id = task_custom_id).filter(user = user.id).first()
report = TimeReport(**validated_data)
report.user = user
report.task = task
report.save()
return report
task = Task.objects.filter(custom_id = validated_data.pop('task_custom_id')).filter(user = user.id).first()
the **validated_data will return (task_custom_id=value, field1=value1 ...) and task_custom_id it's not a TimeReport field so all u need is to pop it from validated_data before calling the constructor TimeReport

'ModelChoiceField' object is not iterable - calling objects from existing Model

Losing the will to live here lol - might be fairly obvious that I'm new to Django/Python.
Can't work out what's going wrong here, I'm calling objects from a model that has values in Django Admin but every time I try to fix one thing, it breaks another.
I want to be able to create a new entry from frontend, but I get this error when trying to migrate:
Stack error
File "/usr/local/lib/python3.6/site-packages/django/forms/fields.py", line 784, in __init__
self.choices = choices
File "/usr/local/lib/python3.6/site-packages/django/forms/fields.py", line 801, in _set_choices
value = list(value)
TypeError: 'ModelChoiceField' object is not iterable
Please see my model below:
models.py
# Model
engineer_shifts = [
(0,'Shift 1'),
(1,'Shift 2'),
(2,'Shift 3')
]
class Engineer(models.Model):
engineer = models.CharField(max_length=200,default="John Smith",verbose_name="Engineer")
engineer_shift = models.IntegerField(choices=engineer_shifts,verbose_name="Shift")
def __str__(self):
return f"{self.engineer}"
class Meta:
verbose_name_plural = 'Engineers'
class CreateHandoff(models.Model):
handoff_pk = models.AutoField(primary_key=True)
handoff_date = models.DateField(auto_now_add=True,verbose_name="Handoff Date")
shift1_lead = models.IntegerField(choices=engineer_picker,verbose_name="Shift 1 Lead")
shift1_sec = models.IntegerField(choices=engineer_picker,verbose_name="Shift 1 Secondary")
def __str__(self):
return f"{self.handoff_date}"
class Meta:
verbose_name_plural = 'Handoffs'
# Form
engineer_picker = forms.ModelChoiceField(
queryset=Engineer.objects.all()
)
class CreateHandoffForm(forms.ModelForm):
shift1_lead = forms.ChoiceField(
label = "Select Shift 1 Lead",
choices = engineer_picker,
required = True
)
shift1_sec = forms.ChoiceField(
label = "Select Shift 1 Secondary",
choices = engineer_picker,
required = True
)
class Meta:
model = CreateHandoff
fields = ["shift1_lead","shift1_sec"]
You are looking for a ForeignKey, you can make a ForeignKey to refer to a model record, so:
class CreateHandoff(models.Model):
handoff_pk = models.AutoField(primary_key=True)
handoff_date = models.DateField(auto_now_add=True,verbose_name="Handoff Date")
shift1_lead = models.ForeignKey(Engineer, on_delete=models.CASCADE, related_name='lead_handoffs', verbose_name='Shift 1 Lead')
shift1_sec = models.ForeignKey(Engineer, on_delete=models.CASCADE, related_name='sec_handoffs', verbose_name='Shift 1 Secondary')
def __str__(self):
return f"{self.handoff_date}"
class Meta:
verbose_name_plural = 'Handoffs'
For the ModelForm, you do not need to specify the fields: Django will automatically make ModelChoiceField for these, so:
class CreateHandoffForm(forms.ModelForm):
class Meta:
model = CreateHandoff
fields = ['shift1_lead', 'shift1_sec']

Django - Redirect to a subclass admin page

I'm creating a web application with Django.
In my models.py I have a class BaseProduct and a class DetailProduct, which extends BaseProduct.
In my admin.py I have BaseProductAdmin class and DetailProductAdmin class, which extends BaseProductAdmin.
I have another class called System, with a many to many relation with BaseProduct.
In the System admin page, I can visualize a list of the BaseProduct objects related to that system.
When I click on a product, the application redirect me to the BaseProduct admin page.
When a product of the list is a DetailProduct object, I would like to be redirected on the DetailProduct admin page instead.
Any idea on how to do this?
In models.py :
class BaseProduct(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
_prod_type_id = models.ForeignKey(
ProductTypes, verbose_name="product type", db_column='_prod_type_ID')
systems = models.ManyToManyField(
'database.System', through='database.SystemProduct')
def connected_to_system(self):
return self.systems.exists()
class Meta:
db_table = u'products'
verbose_name = "Product"
ordering = ['id', ]
class System(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
name = models.CharField(max_length=300)
def has_related_products(self):
""" Returns True if the system is connected with products. """
return self.products_set.exists()
class Meta:
managed = False
db_table = u'systems'
verbose_name = "System"
ordering = ['id', ]
class DetailProduct(BaseProduct):
options_id = models.AutoField(db_column='ID', primary_key=True)
product = models.OneToOneField(BaseProduct, db_column='_product_ID', parent_link=True)
min_height = models.FloatField(help_text="Minimum height in meters.")
max_height = models.FloatField(help_text="Maximum height in meters.")
def __init__(self, *args, **kwargs):
super(DetailProduct, self).__init__(*args, **kwargs)
if not self.pk:
self._prod_type_id = ProductTypes.objects.get(pk=9)
class Meta:
managed = False
db_table = 'detail_product'
verbose_name = "Detail product"
verbose_name_plural = "Detail products"
class SystemProduct(models.Model):
id = models.AutoField(primary_key=True, db_column='ID')
_system_id = models.ForeignKey(System, db_column='_system_ID')
_product_id = models.ForeignKey(BaseProduct, db_column='_Product_ID')
class Meta:
db_table = u'system_product'
unique_together = ('_system_id', '_product_id')
verbose_name = "system/product connection"
In my admin.py page:
class SystemProductInlineGeneric(admin.TabularInline):
model = SystemProduct
extra = 0
show_edit_link = True
show_url = True
class SystemProductForm(forms.ModelForm):
class Meta:
model = SystemProduct
fields = '__all__'
def __init__(self, *args, **kwargs):
""" Remove the blank option for the inlines. If the user wants to remove
the inline should use the proper delete button. In this way we can
safely check for orphan entries. """
super(SystemProductForm, self).__init__(*args, **kwargs)
modelchoicefields = [field for field_name, field in self.fields.iteritems() if
isinstance(field, forms.ModelChoiceField)]
for field in modelchoicefields:
field.empty_label = None
class SystemProductInlineForSystem(SystemProductInlineGeneric):
""" Custom inline, used under the System change page. Prevents all product-system
connections to be deleted from a product. """
form = SystemProductForm
raw_id_fields = ("_product_id",)
class SystemAdmin(admin.ModelAdmin):
inlines = [SystemProductInlineForSystem]
actions = None
list_display = ('id', 'name')
fieldsets = [('System information',
{'fields': (('id', 'name',), ),}),
]
list_display_links = ('id', 'configuration',)
readonly_fields = ('id',)
save_as = True
If I understand correctly, your question is how to change the InlineAdmin (SystemProductInlineForSystem) template so the "change link" redirects to the DetailProduct admin change form (instead of the BaseProduct admin change form) when the product is actually a DetailProduct.
I never had to deal with this use case so I can't provide a full-blown definitive answer, but basically you will have to override the inlineadmin template for SystemProductInlineForSystem and change the part of the code that generates this url.
I can't tell you exactly which change you will have to make (well, I probably could if I had a couple hours to spend on this but that's not the case so...), so you will have to analyze this part of the code and find out by yourself - unless of course someone more knowledgeable chimes in...

How to compare the changes in a modelform with model (django)

I do not know how to compare if a modelform is equal to a model in django.
thank you very much
models.py
class Person(models.Model):
name = models.CharField(max_length=45)
lastname = models.CharField(max_length=45)
dni = models.BigIntegerField()
email = models.EmailField(max_length=30)
status = models.BooleanField()
departament = models.ForeignKey(Departament) #char
forms.py
class Form_Person(forms.ModelForm):
class Meta:
model = models.Person
fields = ['name', 'lastname', 'dni', 'address', 'phone', 'email', 'position', 'status', 'departament']
views.py
#auth.decorators.login_required(login_url='login')
def persons_person(request,id='id'):
page_name = 'Persons'
try:
person = models.Person.objects.get(id=id)
list_departaments = models.Departament.objects.all()
list_departaments = list_departament.exclude(name = person.departament)
if request.method == 'POST':
form_person = forms.Form_Person(request.POST, initial='person')
Here the comparison would be implemented
### code to compare ###
# if form_persona.is_valid() and form_person.has_changed(): #Something like that
# ***how to compare***
# form_person.save()
except models.Person.DoesNotExist as e:
person = None
list_departaments = None
return render(request, 'app/persons/person.html',
{'page_name':page_name,
'person':person,
'list_departaments':list_departaments})
The link in the duplicate flag suggests using save method on object (same can be done in form also). I would personally suggest using signals with pre_save option to check before saving.

GenericTabularInline with a ManyToMany Not Displaying Any Options

I attempt to add a GenericTabularInline to the admin of a related model and the ManyToMany field of the Generic Relation model does not display anything.
models.py
class RelatedIngredients(models.Model):
content_type = models.ForeignKey(ContentType, verbose_name='Content Type')
object_id = models.PositiveIntegerField(verbose_name='Object ID')
content_object = fields.GenericForeignKey('content_type', 'object_id')
ingredients = models.ManyToManyField(Ingredients)
admin.py
class IngredientInline(GenericTabularInline):
model = RelatedIngredients
extra = 0
min_num = 1
max_num = 1
can_delete = False
fields = ['ingredients',]
filter_horizontal = ['ingredients',]
#admin.register(Cake)
class CakeAdmin(admin.ModelAdmin):
fields = ['name', 'description']
inlines = [IngredientInline,]
The proper filter horizontal widget appears but it appears empty. Nothing in the available or chosen sides. Now I know the model works as I tried to do RelatedIngredients.ingredients as a Foreign Key and the GenericTabularInline worked.
What's the difference? Any ideas?

Categories