I have model
#with_author
class Lease(CommonInfo):
version = IntegerVersionField( )
is_renewed = models.BooleanField(default=False)
unit = models.ForeignKey(Unit)
is_terminated = models.BooleanField(default=False)
def __unicode__(self):
return u'%s %i %s ' % ("lease#", self.id, self.unit)
def clean(self):
model = self.__class__
if self.unit and (self.is_active == True) and model.objects.filter(unit=self.unit, is_terminated = False , is_active = True).count() == 1:
raise ValidationError('Unit has active lease already, Terminate existing one prior to creation of new one or create a not active lease '.format(self.unit))
and I have a form
class LeaseForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LeaseForm, self).__init__(*args, **kwargs)
self.fields['unit'].required = True
class Meta:
model = Lease
fields = [ 'unit',
'is_active','is_renewed', 'description']
and each time a save this form without selecting value for unit I am getting
error RelatedObjectDoesNotExist
from my clean function in model since there is no self.unit
but I am explicitly validating the unit field.(at least i believe so)
what am I doing wrong?
Note that full_clean() will not be called automatically when you call
your model’s save() method. You’ll need to call it manually when you
want to run one-step model validation for your own manually created
models. [docs]
This is apparently done for backwards compatibility reasons, check this ticket out.
Model's full_clean() method is responsible for calling Model.clean(), but because it's never been called, your clean method inside model is basically omitted.
You can do couple of things for this. You can call model's clean manually. Or, you can move your validation logic into ModelForm using its clean methods. If you are mainly creating instance via form, I think it's the best place to perform validation (and more common practice).
Try this:
class LeaseForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LeaseForm, self).__init__(*args, **kwargs)
self.fields['unit'].required = True
# IF your validation logic includes multiple fields override this
def clean(self):
cleaned_data = super(LeaseForm, self).clean()
# .. your logic
return cleaned_data
# IF just your field's value is enough for validation override this
def clean__unit(self):
data = self.cleaned_data.get('unit', None)
# .. your logic
return data
class Meta:
model = Lease
fields = [ 'unit',
'is_active','is_renewed', 'description']
Related
I have a model that has class methods. In testing the class methods work and alter the model instances according to my needs. The issue is using this class method in the admin. When an application cannot pay a late payment fee is applied creating another transaction altering the balance. The method in models is decorated with a #classmethod decorator:
class Transactions(models.Model):
application = models.ForeignKey(Application,
related_name='application_id', blank=True, null=True)
transaction_type = models.CharField(max_length=56,
choices=TRANSACTION_TYPE, null=True)
transaction_source = models.CharField(max_length=56,
choices=TRANSACTION_SOURCE, null=True)
transaction_method = models.CharField(max_length=56,
choices=TRANSACTION_METHOD, null=True)
amount = models.DecimalField(max_digits=10, decimal_places=2)
created_date = models.DateField()
posted_date = models.DateField()
external_reference = models.CharField(max_length=100, null=True,
verbose_name='External Reference')
def __unicode__(self):
return self.application.forename + " " +
self.application.surname + "(" + str(self.application.id) + ")"
#classmethod
def late_payment_fee(cls, app, desired_action):
"""
This function enacts a late payment fee to an application as a
transaction
:param app: application the fee is going to be applied to
:param desired_action: either True or False, when reversing the
fee the transaction shouldn't be deleted,
just another transaction of the opposite effect in order to
help loans collection with tracking, True will
enact a feee, False will reverse the fee
:return: a transaction which is stored in the database
"""
today = str(date.today())
if desired_action:
trans_type = MISSEDREPAYMENTFEE
amount = float(12)
else:
trans_type = MISSEDREPAYMENTFEEREVERSAL
amount = float(-12)
cls.create_trasaction(app, trans_type, INTERNALBOOKING,
INTERNALBOOKING, amount, today, today, None)
I need to get it so when status is altered, or when a tickbox is checked in the admin for an application it fires the class method. I have Googled overriding models in admin but cannot find anything. Here's the admin:
class ApplicationAdmin(ImportExportModelAdmin):
resource_class = ApplicationResource
search_fields = ['forename', 'surname']
list_filter = ('status', 'underwritingresult', 'test', 'signed',
'mandateapproved', 'dealership', 'brand')
list_select_related = ('instalment',)
list_display = ('id', 'SPV_ID', 'forename', 'surname'......
inlines = [
InstalmentInline,
AddressInline
]
exclude = ['customer', 'accountnumber', 'sortcode']
readonly_fields = ('Account_Number', 'Sort_Code', 'SPV_ID')
def get_readonly_fields(self, request, obj=None):
readonly_fields = []
if obj.locked :
for field in self.model._meta.fields:
readonly_fields.append(field.name)
else:
readonly_fields = ('Account_Number', 'Sort_Code', 'SPV_ID')
return readonly_fields
def Account_Number(self, obj):
return Decrypt(obj.accountnumber)
def Sort_Code(self, obj):
return Decrypt(obj.sortcode)
def SPV_ID(self, obj):
return obj.spv.id
def has_delete_permission(self, request, obj=None):
return False
Many thanks for reading this.
I've now found the solution (sorry if my initial question was not very clear). It turned out that I needed to override the save function in the Application model. However, another issue was raised when doing this. If a user changed something benign like the name of the customer then the class method would fire and create new transactions even though there was no real change. Because of this, we have to override the init and the save function in the model but still actually save. For this model, we are interested to see if the status has changed or a late fee has been applied. In order to do this, we have to store the initial status values by overriding the init method in the model:
__initial_status = None
__initial_fee_status = None
def __init__(self, *args, **kwargs):
"""
This function overrides the __init__ function in order to
save initial_status facilitating comparisons in the save function
:param args: allows the function to accept an arbitrary number of
arguments and/or keyword arguments
:param kwargs: same as above
"""
super(Application, self).__init__(*args, **kwargs)
self.__initial_status = self.status
self.__initial_fee_status = self.feeadded
Now we have them stored we can pass them through our save function. If there is a change we can utilize the class method:
def save(self, *args, **kwargs):
"""
This function overrides the standard save function. The application
is still saved, however, other functions are fired when it is
saved.
:return: creates reversal commission and dealership transactions
if the status is CANCELLED
"""
if self.signed and self.status != self.__initial_status:
if self.status == Application.CANCELLED:
Transactions.create_commision_trasaction(app=self,
reverse=True)
Transactions.create_dealership_trasaction(app=self,
reverse=True)
elif self.status == Application.OK:
Transactions.create_commision_trasaction(app=self,
reverse=False)
Transactions.create_dealership_trasaction(app=self,
reverse=False)
if self.signed and self.feeadded != self.__initial_fee_status:
if self.feeadded:
Transactions.late_payment_fee(app=self, desired_action=True)
elif self.feeadded is False:
Transactions.late_payment_fee(app=self, desired_action=False)
super(Application, self).save(*args, **kwargs)
I can have custom validators for my django models and what I would like to do is perform validation at the form level where the form elements have dependencies with each other. To illustrate, say I have the following model:
class MyModel(models.Model):
num_average = models.IntegerField(verbose_name='Number of averages',
default=1)
num_values = models.IntegerField(verbose_name='Number of values',
default=3)
The dependency is that num_values = num_average * 3. I know I can set this automatically but for this purposes let us assume we want the user input. I have a form as:
class MyForm(ModelForm):
class Meta:
model = MyModel
fields = ['num_average', 'num_values']
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
Is there a way to validate the form as a whole before the submit gets triggered?
Yes, as the form docs point out, this kind of thing is done in a clean method.
class MyForm(ModelForm):
class Meta:
model = MyModel
fields = ['num_average', 'num_values']
def clean(self):
data = self.cleaned_data
if data['num_values'] != data['num_average'] *3:
raise forms.ValidationError('values must be three times average')
As an aside, you shouldn't define __init__ if you're not doing anything with it; overriding a method just to call the superclass method is pointless.
I have a form:
class CourseStudentForm(forms.ModelForm):
class Meta:
model = CourseStudent
exclude = ['user']
for a model with some complicated requirements:
class CourseStudent(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
semester = models.ForeignKey(Semester)
block = models.ForeignKey(Block)
course = models.ForeignKey(Course)
grade = models.PositiveIntegerField()
class Meta:
unique_together = (
('semester', 'block', 'user'),
('user','course','grade'),
)
I want the new object to use the current logged in user for CourseStudent.user:
class CourseStudentCreate(CreateView):
model = CourseStudent
form_class = CourseStudentForm
success_url = reverse_lazy('quests:quests')
def form_valid(self, form):
form.instance.user = self.request.user
return super(CourseStudentCreate, self).form_valid(form)
This works, however, because the user is not part of the form, it misses the validation that Django would otherwise do with the unique_together constraints.
How can I get my form and view to use Django's validation on these constraints rather than having to write my own?
I though of passing the user in a hidden field in the form (rather than exclude it), but that appears to be unsafe (i.e. the user value could be changed)?
Setting form.instance.user in form_valid is too late, because the form has already been validated by then. Since that's the only custom thing your form_valid method does, you should remove it.
You could override get_form_kwargs, and pass in a CourseStudent instance with the user already set:
class CourseStudentCreate(CreateView):
model = CourseStudent
form_class = CourseStudentForm
success_url = reverse_lazy('quests:quests')
def get_form_kwargs(self):
kwargs = super(CreateView, self).get_form_kwargs()
kwargs['instance'] = CourseStudent(user=self.request.user)
return kwargs
That isn't enough to make it work, because the form validation skips the unique together constraints that refer to the user field. The solution is to override the model form's full_clean() method, and explicitly call validate_unique() on the model. Overriding the clean method (as you would normally do) doesn't work, because the instance hasn't been populated with values from the form at that point.
class CourseStudentForm(forms.ModelForm):
class Meta:
model = CourseStudent
exclude = ['user']
def full_clean(self):
super(CourseStudentForm, self).full_clean()
try:
self.instance.validate_unique()
except forms.ValidationError as e:
self._update_errors(e)
This worked for me, please check. Requesting feedback/suggestions.
(Based on this SO post.)
1) Modify POST request to send the excluded_field.
def post(self, request, *args, **kwargs):
obj = get_object_or_404(Model, id=id)
request.POST = request.POST.copy()
request.POST['excluded_field'] = obj
return super(Model, self).post(request, *args, **kwargs)
2) Update form's clean method with the required validation
def clean(self):
cleaned_data = self.cleaned_data
product = cleaned_data.get('included_field')
component = self.data['excluded_field']
if Model.objects.filter(included_field=included_field, excluded_field=excluded_field).count() > 0:
del cleaned_data['included_field']
self.add_error('included_field', 'Combination already exists.')
return cleaned_data
I'm building my first real django application which has different user roles and the normal "User signal that creates an UserProfile" approach is falling short. Could you guys help me out ?
To give out more context, here are the requirements from a functional perspective :
New users are added only from the Admin and will be done by non-tech savy people, so I need a user creation flow thats intuitive and easy.
Each user has a role (Manager, Employee, Salesman, etc) with different needs and fields.
The user list needs to show both User Information and role / profile information (login, email, name, and the extra information on profile)
Initial approach :
So armed with this, I went with the recommended approach of creating a UserProfile 1-to-1 object linked to user, provide the UserProfile with a choice field where the role is set (useful for knowing what i'm dealing with when calling get_profile() ) and subclassed UserProfile into ManagerUserProfile, EmployeeUserProfile, etc.
Problem :
That works for my needs in the frontend (outside of the admin), but setting the signal to create a UserProfile when a User is created is pointless since I don't know what kind of UserProfile should I create based only on user information.
What I'm aiming at is an atomic way of creating a particular User and it's corresponding EmployeeUserProfile/ManagerUserProfile at the same time, and having a neat admin representation.
Some of my ideas:
Hide the UserAdmin and User Profile admins, create AdminModels for EmployeeUserProfile/Manager/etc and inline the User model. That way the person creating the users will see only a "New Manager" link with its corresponding fields. But they may create the UserProfile without a user ? How can i make this atomic ? How do i prevent from deleting the user within or make sure they provide all required info before allowing the profile to be saved ? -> Problem with this approach : I cannot seem to inline the user because it has no PK to UserProfile (it's the other way around).
Again, hide UserAdmin, expose the subclassed profiles Admins, and reverse the signal. When a profile is created, create a corresponding user. But for this I need to be able to provide user fields (username, pass, email, etc) from profiles admin form.
Suggestions ?
Its my first app and maybe there's a neat way for this, but I haven't found it yet.
Thanks for taking the time to read this.
Cheers,
Zeta.
I would suggest that you create a custom form, custom admin view and use that for creating users in one request, with the exact logic you need. You can get an idea of how it's done by looking at django's own custom user creation process here.
I finally found a way to implement what I needed. It may not be the cleanest, and it's open to suggestions, but it may help someone with a similar problem.
Since I needed to create only from the admin, I focused on that and build the following.
forms.py
class RequiredInlineFormSet(BaseInlineFormSet):
"""
Generates an inline formset that is required
"""
def _construct_form(self, i, **kwargs):
"""
Override the method to change the form attribute empty_permitted
"""
form = super(RequiredInlineFormSet, self)._construct_form(i, **kwargs)
form.empty_permitted = False
self.can_delete = False
return form
models.py (i'm not using signals for automatically creating a profile on user creation)
class UserProfile(models.Model):
# This field is required.
user = models.OneToOneField(User)
# Other fields here
[.......]
USER_TYPES = (
('manager', 'Manager'),
('employee', 'Employee'),
)
user_type = models.CharField(blank=True, max_length=10, choices=USER_TYPES)
def __unicode__(self):
return self.user.username
class EmployeeProfile(UserProfile):
[...]
def __init__(self, *args, **kwargs):
super(EmployeeProfile, self).__init__(*args, **kwargs)
self.user_type = 'employee'
class ManagerProfile(UserProfile):
[...]
def __init__(self, *args, **kwargs):
super(ManagerProfile, self).__init__(*args, **kwargs)
self.user_type = 'manager'
class Manager(User):
class Meta:
proxy = True
#app_label = 'auth'
verbose_name = 'manager'
verbose_name_plural = 'managers'
def save(self, *args, **kwargs):
self.is_staff = True
super(Manager, self).save(*args, **kwargs) # Call the "real" save() method.
g = Group.objects.get(name='Managers')
g.user_set.add(self)
class Employee(User):
class Meta:
proxy = True
#app_label = 'auth'
verbose_name = 'employee'
verbose_name_plural = 'employees'
def save(self, *args, **kwargs):
self.is_staff = False
super(Employee, self).save(*args, **kwargs) # Call the "real" save() method.
g = Group.objects.get(name='Employees')
g.user_set.add(self)
admin.py
class ManagerProfileAdmin(admin.StackedInline):
model = ManagerProfile
max_num = 1
extra = 1
formset = RequiredInlineFormSet
class EmployeeProfileAdmin(admin.StackedInline):
model = EmployeeProfile
max_num = 1
extra = 1
formset = RequiredInlineFormSet
class ManagerAdmin(UserAdmin):
"""
Options for the admin interface
"""
inlines = [ManagerProfileAdmin]
def queryset(self, request):
qs = super(UserAdmin, self).queryset(request)
qs = qs.filter(Q(userprofile__user_type='manager'))
return qs
class EmployeeAdmin(UserAdmin):
"""
Options for the admin interface
"""
inlines = [EmployeeProfileAdmin]
def queryset(self, request):
qs = super(UserAdmin, self).queryset(request)
qs = qs.filter(Q(userprofile__user_type='employee'))
return qs
admin.site.unregister(User)
admin.site.register(Manager, ManagerAdmin)
admin.site.register(Employee, EmployeeAdmin)
I'm using Django profiles and was inspired by James Bennett to create a dynamic form (http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ )
What I need is a company field that only shows up on my user profile form when the user_type is 'pro'.
Basically my model and form look like:
class UserProfile(models.Model):
user_type = models.CharField(...
company_name = models.CharField(...
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('company_name',)
And I add the company_name field in init like James Bennett showed:
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
self.fields['company_name'] = forms.CharField(...
The problem is that, when I try to save() an instance of UserProfileForm, the field 'company_name' is not saved...
I have gone around this by calling the field explicitly in the save() method:
def save(self, commit=True):
upf = super(UserProfileForm, self).save(commit=False)
if 'company_name' in self.fields:
upf.company_name = self.cleaned_data['company_name']
if commit:
upf.save()
return upf
But I am not happy with this solution (what if there was more fields ? what with Django's beauty ? etc.). It kept me up at night trying to make the modelform aware of the new company_name field at init .
And that's the story of how I ended up on stackoverflow posting this...
I would remove this logic from form and move it to factory. If your logic is in factory, you can have two forms:
UserProfileForm
ProUserProfileForm
ProUserProfileForm inherits from UserProfileForm and changes only "exclude" constant.
You will have then following factory:
def user_profile_form_factory(*args, instance=None, **kwargs):
if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
cls = ProUserProfileForm
else:
cls = UserProfileForm
return cls(*args, instance, **kwargs)
It seems I found a solution:
def AccountFormCreator(p_fields):
class AccountForm(forms.ModelForm):
class Meta:
model = User
fields = p_fields
widgets = {
'photo': ImageWidget()
}
return AccountForm
#...
AccountForm = AccountFormCreator( ('email', 'first_name', 'last_name', 'photo', 'region') )
if request.POST.get('acforms', False):
acform = AccountForm(request.POST, request.FILES, instance=request.u)
if acform.is_valid():
u = acform.save()
u.save()
ac_saved = True
else:
acform = AccountForm(instance = request.u)
When are you expecting the user_type property to be set? This seems like something that should be handled by javascript rather than trying to do funny things with the model form.
If you want the company_name field to appear on the client after they've designated themselves as a pro, then you can 'unhide' the field using javascript.
If instead, they've already been designated a pro user, then use another form that includes the company_name field. You can sub-class the original model form in the following manner.
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('company_name',)
class UserProfileProForm(UserProfileForm):
class Meta:
exclude = None # or maybe tuple() you should test it
Then in your view, you can decide which form to render:
def display_profile_view(request):
if user.get_profile().user_type == 'Pro':
display_form = UserProfileProForm()
else:
display_form = UserProfileForm()
return render_to_response('profile.html', {'form':display_form}, request_context=...)
This would be the preferred way to do it in my opinion. It doesn't rely on anything fancy. There is very little code duplication. It is clear, and expected.
Edit: (The below proposed solution does NOT work)
You could try changing the exclude of the meta class, and hope that it uses the instances version of exclude when trying to determine whether to include the field or not. Given an instance of a form:
def __init__(self, *args, **kwargs):
if self.instance.user_type == 'pro':
self._meta.exclude = None
Not sure if that will work or not. I believe that the _meta field is what is used after instantiation, but I haven't verified this. If it doesn't work, try reversing the situation.
def __init__(self, *args, **kwargs):
if self.instance.user_type != 'pro':
self._meta.exclude = ('company_name',)
And remove the exclude fields altogether in the model form declaration. The reason I mention this alternative, is because it looks like the meta class (python sense of Meta Class) will exclude the field even before the __init__ function is called. But if you declare the field to be excluded afterwards, it will exist but not be rendered.. maybe. I'm not 100% with my python Meta Class knowledge. Best of luck.
What about removing exclude = ('company_name',) from Meta class? I'd think that it is the reason why save() doesn't save company_name field