Setting the initial value for a readonly Choice (ForeignKey) field - django

I'm trying to make a CreateView have a readonly field with a set value, but I'm unable to make that work.
I have a model with a ForeignKey to another model:
class CompanyNote(TimeStampedModel):
company = models.ForeignKey(Company)
note = models.TextField(blank=True)
And I have a CreateView:
class CompanyNoteCreateView(CreateView):
model = models.CompanyNote
form_class = CompanyNoteForm
That uses a custom ModelForm:
class CompanyNoteForm(forms.ModelForm):
company = forms.ChoiceField(
widget=forms.widgets.Select(attrs={'readonly': 'readonly'}))
class Meta:
model = models.CompanyNote
As you see, the widget for the field in question is readonly. This is because I pick up the company as a part of the URL, as in company/1/note/add . I have no trouble picking up the "1" and finding the right company object, but I don't know how to set the readonly field.
I tried:
def get_initial(self):
initial = super(CompanyNoteCreateView, self).get_initial()
initial['company'] = self.get_company().id
return initial
But that didn't work. The Widget is empty, which may be the problem. Perhaps I'm barking up the wrong tree here. Any ideas welcome.

Have you tried setting the attribute in the Form's Meta class?
I experienced an issue where Form attributes were not applied for Model Fields if set in the base class definition, but they worked correctly in the Meta class:
class CompanyNoteForm(forms.ModelForm):
class Meta:
model = models.CompanyNote
widgets = {'company': forms.widgets.Select(attrs={'readonly': True,
'disabled': True})}
Otherwise check this answer out.
Worst case scenario, make company a hidden field?

Use a ModelChoiceField
class CompanyNoteForm(forms.ModelForm):
company = forms.ModelChoiceField(queryset=models.Company.objects.all(), widget=forms.widgets.Select(attrs={'readonly': 'readonly'}))

I could not find this answer anywhere, that I could actually get to work. But I found a different approach. Set the field to be hidden with forms.HiddenInput() widget. Then the value you pass in from the view will be assigned but the user cannot access it.
widgets = {'field_name': forms.HiddenInput()}
I'm using ModelForm class so my syntax might be different from yours.

Related

Can I override default CharField to ChoiceField in a ModelForm?

I have a (horrible) database table that will be imported from a huge spreadsheet. The data in the fields is for human consumption and is full of "special cases" so its all stored as text. Going forwards, I'd like to impose a bit of discipline on what users are allowed to put into some of the fields. It's easy enough with custom form validators in most cases.
However, there are a couple of fields for which the human interface ought to be a ChoiceField. Can I override the default form field type (CharField)? (To clarify, the model field is not and cannot be constrained by choices, because the historical data must be stored. I only want to constrain future additions to the table through the create view).
class HorribleTable( models.Model):
...
foo = models.CharField( max_length=16, blank=True, ... )
...
class AddHorribleTableEntryForm( models.Model)
class Meta:
model = HorribleTable
fields = '__all__' # or a list if it helps
FOO_CHOICES = (('square', 'Square'), ('rect', 'Rectangular'), ('circle', 'Circular') )
...?
Perhaps you could render the forms manually, passing the options through the context and make the fields in html.
Take a look at here:https://docs.djangoproject.com/en/4.0/topics/forms/#rendering-fields-manually
I think you can easily set your custom form field as long it will match the data type with the one set in your model (e.g. do not set choices longer than max_length of CharField etc.). Do the following where foo is the same name of the field in your model:
class AddHorribleTableEntryForm(forms.ModelForm):
foo = forms.ChoiceField(choices=FOO_CHOICES)
class Meta:
model = HorribleTable
...
I think this is perfectly fine for a creation form. It's will not work for updates as the values in the DB will most probably not match your choices. For that, I suggest adding a second form handling data updates (maybe with custom permission to restrict it).
UPDATE
Another approach will be to override the forms init method. That way you can handle both actions (create and update) within the same form. Let the user select from a choice field when creating an object. And display as a normal model field for existing objects:
class AddHorribleTableEntryForm(forms.ModelForm):
foo = forms.ChoiceField(choices=FOO_CHOICES)
class Meta:
model = HorribleTable
fields = '__all__' # or a list if it helps
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get("instance", None)
if instance is None:
self.fields["foo"].widget = forms.widgets.Select(choices=self.FOO_CHOICES)

Model Method from rest_framework modelSerializer

this is a simple question but I'm very new to django-rest-framework.
I was wondering if there is any way to access a method defined on the model from the serializer.?
Say I have a model
class Listing(models.Model):
listingid = models.BigIntegerField(primary_key=True)
mappingid = models.BigIntegerField()
projectlevelid = models.IntegerField()
subsellerid = models.IntegerField()
iscreatedbyadmin = models.BooleanField(default=None, null=True)
createdon = models.DateTimeField(auto_now_add=True, editable=False)
validationstatus = models.SmallIntegerField(default=0)
def is_project(self):
""" Returns True if listing is of Project Type (projectlevelid=6) else False"""
if self.projectlevelid == 6:
return True
else:
return False
def get_project_info(self):
"""Returns False if listing is not mapped to a project, else returns the project info"""
if self.is_project() == False:
return False
return models.Project.objects.get(projectid=self.mappingid)
Is it possible for the serializer
class ListingSerializer(serializers.ModelSerializer):
class Meta:
model = models.MasterListing
to have access to Listing.is_project i.e. for an object of the Listing model, can the serializer call its is_project method?
If so, can I set a field in the serializer such that if is_project returns true, the field is populated?
I am trying for something like this,
class ListingSerializer(serializers.ModelSerializer):
project = serializers.SomeRELATEDFieldTYPE() # this field if populated if the `is_project` is true
class Meta:
model = models.MasterListing
I understand I can do this using some combination of required=False and SerializerMethodField, but maybe there is a simpler way?.
Note: It is not possible for me to set a foreign key to the mappingid, since it depends on the projectlevelid. I also can't affect this relationship so no further normalization is possible. I know that there might be some way using content-types, but we are trying to avoid that if it is possible..
EDIT: I solved the problem, but not as the question specified.
I used this:
class ListingSerializer(serializers.ModelSerializer):
project = serializers.SerializerMethodField()
def get_project(self, obj):
"""Returns False if listing is not mapped to a project, else returns the project info"""
if str(obj.projectlevelid) == str(6):
projectObj = models.Project(projectid=obj.mappingid)
projectObjSerialized = ProjectSerializer(projectObj)
return projectObjSerialized.data
return False
class Meta:
model = models.MasterListing
So, the original question still stands: "Is it possible for the modelSerializer to access its models methods?"
Also, another problem that now appears is, can I make the serializer exclude fields on demand, i.e. can it exclude mappingid and projectlevelid if it is indeed a project?
For your first question source attribute is the answer, citing:
May be a method that only takes a self argument, such as
URLField('get_absolute_url')
For your second answer, yes it is also possible. Check the example it provides in their docs: http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
PS: I really love drf for its very complete documentation =).
EDIT
To use the source attribute you can just declare a new explicit field like so:
is_project = serializers.BooleanField(source='is_project')
With this, is_project field has the value of the is_project method of your instance. Having this, when creating the dynamic serializer (by modifying its init method) you can add the 'project' field if it's True.
#argaen is absolutely right, source is a DRF core argument, and would most definitely solve your problem. However, it's redundant to use source, if the field name is the same as the source. So the above answer won't require you specify source, since field name is_project is the same as source name is_project.
So in your case:
is_project = serializers.BooleanField()

django is there some way to pick up value from ModelForm before it gets validated?

Assume these as django models:
class Author():
name = charfield()
class Book():
title = charfield()
author = foreignkey()
and a ModelForm:
class BookForm():
# i override the author field
author = models.CharField()
class Meta:
model = Book
fields = ('title', 'author')
I'm using an autocomplete plugin (like facebook search, so i can fill the author field without using choicefield).The problem is, i cannot assign value from author field because it is not an instance of author, it is a string. So i'm thinking to manipulate it before it gets validated. I tried modifying the QueryDict but is immutable. As soon as i invoke the form with form = BookForm(request.POST) it gets the error : Cannot assign u"foo":"Book.author" must be an Author instance. Thank you.
You are not doing it the "django way". you need to write your own widget for the author field.
and do something like this:
class BookForm():
class Meta:
model = Book
fields = ('title', 'author')
widgets = {
'author': YourFacebookLikeWidgetClass,
}
Now, your widget is responsible for doing all your cool stuff, (ajax call, rendering the results...etc) , but in the end, it will return the right value for the form. (the author ID)
Read more about django widgets
The problem is that there isnt a lot of info about writing your own widgets, but you can always ask here, and watch the source code of some django widgets. (It is not vert complicated to write your own :) )

How can I limit fields in ToManyField with full=True in django-tastypie

I have the following resource:
class MachineResource(ModelResource):
manager = fields.ToOneField(UserResource, 'manager',full=True)
class Meta:
queryset = Service.objects.filter(service_type='machine')
resource_name = 'machine'
This works fine. And will return a list of machines, and an embedded user object (the manager) in each.
However, I only one want 2-3 fields from the manager user. I dont want it to contain the managers salted pass and other private data for example.
As far as I can see there isn't a way I can do this easily?
Just take a look at the Quick Start section for django-tastypie. There's a perfect example right there. When you define your ModelResource subclass for User (your "manager"), simply add a Meta class with an exclude attribute set to the list of fields you don't want to show.
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'auth/user'
excludes = ['email', 'password', 'is_superuser']
I personally find the notion of creating two ModelResources for the same Model class a bit inelegant. For instance, suppose you wanted to display the email field in the detail view of UserResource but not while being displayed as a full object as part of the MachineResource. The way I would solve your problem is by deleting the non-required field's key in the data dictionary of the embedded object in the dehydrate method. A bit hacky way maybe, but works fine for me. For your case, you can do:
class MachineResource(ModelResource):
manager = fields.ToOneField(UserResource, 'manager',full=True)
class Meta:
queryset = Service.objects.filter(service_type='machine')
resource_name = 'machine'
def dehydrate(self,bundle):
del bundle.data['manager'].data['email']
return bundle

How to save fields that are not part of form but required in Django

I have a model with a field that is required but not entered by the user and i have a hard time saving the model without errors. My model definition looks like this:
class Goal(db.Model):
author = db.UserProperty(required=True)
description = db.StringProperty(multiline=True, required=True)
active = db.BooleanProperty(default=True)
date = db.DateTimeProperty(auto_now_add=True)
class GoalForm(djangoforms.ModelForm):
class Meta:
model = Goal
exclude = ['author', 'active']
And i use django-forms in appengine to create and validate the form. When i try to save the result of this form however....
def post(self):
data = GoalForm(data=self.request.POST)
if data.is_valid():
goal = data.save(commit=False)
goal.author = users.get_current_user()
goal.put()
self.redirect('/')
I get "ValueError: The Goal could not be created (Property author is required)"
Now i would think that by having commit=False, then adding the property for Goal, and then saving the object would allow me to do this but obviously it's not working. Any ideas?
Note that save() will raise a ValueError if the data in the form doesn't validate
You can find what you need about the save() method here:
http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
Edit: Instead of goal.put(), do a goal.save()
Edit2: This should solve your problem:
goal = Goal(author='Mr') #example
data = GoalForm(data=self.request.POST, instance=goal)
I realize this is an old question, but for the sake of others searching for a similar answer, I'm posting the following:
Unless there's a reason I missed for not doing so, but I believe this is what you need:
class Goal(db.Model):
author = db.UserProperty(auto_current_user_add=True)
...
...
For reference:
Types and Property Classes:class UserProperty()
Your GoalForm should inherit from django.forms.ModelForm and be defined such that it only requires some fields:
class GoalForm(django.forms.ModelForm):
class Meta:
model = Goal
fields = ('description', 'etc')
Not sure if this is totally working in AppEngine though.
You should also save the form (still not sure about AppEngine):
data = GoalForm(data=self.request.POST)
if data.is_valid():
data.author = users.get_current_user()
data.put()