I am trying to make a registration key in the UserModel where the key field in the registration form is a foreign key to another model called RegistrationKey. I have made two posts about this topic earlier without any success, however has a few things changed in my code which makes those previous posts irrelevant. In the form field, the field for the key is a CharField as I can not display the keys for the users due to safety.
These are the two previous posts:
Save user input which is a string as object in db ,
Textinput with ModelChoiceField
These are my two models.
class RegistrationKey(models.Model):
key = models.CharField(max_length=30)
def __str__(self):
return self.key
class User(AbstractUser):
key = models.ForeignKey(RegistrationKey, on_delete=models.CASCADE, null=True, blank=True)
Since my latest posts have I created a class based view, which looks like this:
class RegisterPage(CreateView):
form_class = MyUserCreationForm
def form_valid(self, form):
key = form.cleaned_data['key']
try:
keyobject = RegistrationKey.objects.get(key=key)
form.instance.key = keyobject
return super().form_valid(form)
except RegistrationKey.DoesNotExist:
form.add_error('key', 'error')
return super().form_invalid(form)
When I try and pass in the value Admin which is an object in the RegistrationKey model I get the following error:
'Cannot assign "'Admin'": "User.key" must be a "RegistrationKey" instance.'
I don't know how to solve this, how can this string that the user inputs be assigned to the db?
Edit
Here are my form
class MyUserCreationForm(UserCreationForm):
key = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control', 'placeholder':'Key'}), label='')
email = forms.EmailField(widget=forms.EmailInput(attrs={'class':'form-control', 'placeholder':'Email'}), label='')
class Meta:
model = User
fields = ('key', 'email', 'password1', 'password2')
def __init__(self, *args, **kwargs):
super(MyUserCreationForm, self).__init__(*args, **kwargs)
self.fields['password1'].widget.attrs['class'] = 'form-control'
self.fields['password1'].widget.attrs['placeholder'] = 'Password'
self.fields['password1'].label=''
self.fields['password2'].widget.attrs['class'] = 'form-control'
self.fields['password2'].widget.attrs['placeholder'] = 'Confirm Password'
self.fields['password2'].label=''
for fieldname in ['password1', 'password2']:
self.fields[fieldname].help_text = None
You better move the logic to obtain the item to the form, where it belongs. So with:
from django.core.exceptions import ValidationError
class MyUserCreationForm(UserCreationForm):
# …
def clean_key(self):
key = self.cleaned_data['key']
try:
return RegistrationKey.objects.get(key=key)
except RegistrationKey.DoesNotExist:
raise ValidationError('The key is not valid.')
That should be sufficient. You should not override the .form_valid(…) method.
Related
I am putting together a Non Disclosure Form(NDA) on my clients site and need to add some field comparison validation. When someone wants more information on one of my clients listings, they would register a name and email (AllAuth) and upon verifying their email, they would immediately be shown the NDA form which would have already inserted their user.first_name and user.last_name at the top of the form. At the bottom of the form after other fields to fill out, they would type their signature in a field that would have the validation -{{ nda.user_signature }}.
I did my best to write a filter that would concatenate the Django user fields to create a signature variable and then compare the value of user_signature matches, if not, it would raise an error. Below is custom tag attempt. Any thoughts are greatly appreciated. Thank you.
from django import template
from django.db.models import F
from django.forms import forms
register = template.Library()
#register.filter(name='confirm_sig')
def confirm_sig(value):
delta = value
signature = F('user.first_name') + "" + F('user.last_name')
if delta != signature:
raise forms.ValidationError("Signatures must match first and last name")
return delta
UPDATED
Here is my form currently
class BaseModelForm(ModelForm):
def __init__(self, *args, **kwargs):
kwargs.setdefault('auto_id', '%s')
kwargs.setdefault('label_suffix', '')
super().__init__(*args, **kwargs)
for field_name in self.fields:
field = self.fields.get(field_name)
if field:
field.widget.attrs.update({
'placeholder': field.label,
'class': 'form-control placeholder-no-fix'
})
class NonDisclosureForm(BaseModelForm):
class Meta:
model = NonDisclosure
fields = ['user_signature', 'user_street', 'user_email', 'user_city', 'user_state', 'user_zip', 'phone', 'cash_on_hand', 'value_of_securities', 'equity_in_real_estate', 'other']
class NdaCreate(CreateView):
form_class = NonDisclosureForm
template_name = 'nda/nda_form.html'
def form_valid(self, form):
form.instance.user = Profile.objects.get(user=self.request.user)
form.instance.created_by = self.request.user
return super(NdaCreate, self).form_valid(form)
I'm trying to create a form from this model:
class A(models.Model):
u = models.OneToOneField(User)
and then create this form:
class AForm(ModelForm):
class Meta:
model = A
fields = ['u']
then i create an instance of that form in my view and send it to my template as a context I'll get a drop down list to choose from existing users but what i want to do is to have a text field to change my current user's first name or last name.
I'll be grateful if you could help me to change my form class to get the right result.
Thank you
You can add the first and last name fields to the AForm ModelForm in the following way:
class AForm(ModelForm):
first_name = forms.CharField(max_length=30, blank=True)
last_name = forms.CharField(max_length=30, blank=True)
class Meta:
Model = A
def __init__(self, *args, **kwargs):
super(AForm, self).__init__(*args, **kwargs)
self.fields['first_name'].initial = self.instance.u.first_name
self.fields['last_name'].initial = self.instance.u.last_name
def save(self, commit=True):
self.instance.u.first_name = self.cleaned_data['first_name']
self.instance.u.last_name = self.cleaned_data['last_name']
self.instance.u.save()
return super(AForm, self).save(commit=commit)
In this case you do not need a modelform of A but a modelform of User. You would need to set the form's instance appropriately in the view. For example,
a_record = A.objects.get_object_or_404(A, id=1)
form = self.UserForm(instance=a.u) # UserForm is a modelform of User
I have a problem similar to this one: Additional (non model) fields in ModelSerializer
I want to create a object with a modelserializer like this:
class CreateUserSerializer(serializers.ModelSerializer):
user = serializers.CharField(source="username")
password = serializers.CharField()
password2 = serializers.WritableField()
...
class Meta:
model = User
fields = ('user', "password", "password2", ...)
Obviously, I am not interested in saving password2, it's just for the reason of comparison.
Ideally this should do the trick but for reasons, its not working as expected:
def restore_object(self, attrs, instance=None):
tags = attrs.pop('password2', None)
obj = super(CreateUserSerializer, self).restore_object(attrs, instance)
obj.tags = tags
logging.info(u"Object gets restored, field 'password2' gets removed, list with arguments for object: {0}".format(tags))
return obj
The Error I am getting is:
'User' object has no attribute 'password2'
...
/home/jan/projekte/alarmapp/eclipse_workspace/AlarmServer/AlarmApp/src/external/rest_framework/fields.py in get_component
55. val = getattr(obj, attr_name)
Any Idea why this isn't working?
Seems like this small Gist should be helpful:
from django.contrib.auth.models import User
from rest_framework import serializers
class CreateUserSerializer(serializers.ModelSerializer):
password2 = serializers.CharField()
def validate_password2(self, attrs, source):
password2 = attrs.pop(source)
if attrs['password'] != password2:
raise serializers.ValidationError('password mismatch')
return attrs
def to_native(self, obj):
self.fields.pop('password2')
return super(CreateUserSerializer, self).to_native(obj)
class Meta:
model = User
I defined a model that has some foreign keys to other models. Such that I have the following in models.py:
class Appelation(models.Model):
appelation = models.CharField(max_length=100,
verbose_name=_('Appelation'),
validators=[non_numeric],
blank=True,
unique=True
)
class Wine(models.Model):
appelation = models.ForeignKey(ForeignKeyModel, null=True, blank=True, verbose_name=_('Appelation'))
forms.py
class WineForm(ModelForm):
class Meta:
model = Wine
appelation= CharField(widget=TextInput)
views.py
class WineCreateView(WineActionMixin, LoginRequiredMixin, CreateView):
model = Wine
form_class = WineForm
action = 'created'
def post(self, *args, **kwargs):
self.request.POST = self.request.POST.copy() # makes the request mutable
appelationForm = modelform_factory(Appelation, fields=('appelation',))
form_dict = {
'appelation': appelationForm
}
for k, modelForm in form_dict.iteritems():
model_class = modelForm.Meta.model
log.debug('current model_class is: %s' % model_class)
log.debug('request is %s' % self.request.POST[k])
try:
obj = model_class.objects.get( **{k: self.request.POST[k]} )
log.debug("object exists. %s pk from post request %s " % (model_class,obj.pk))
self.request.POST[k] = obj.id
except ObjectDoesNotExist as e:
log.error('Exception %s' % e)
f = modelForm(self.request.POST)
log.debug('errors %s' % f.errors)
if f.is_valid():
model_instance = f.save()
self.request.POST[k] = model_instance.pk
return super(WineCreateView,self).post(self.request, *args, **kwargs)
Basically, what the view code does is, it tries to create a new Appelation model instance ( which is a fk to Wine) if the one we passed does not exist. and it returns the pk in the field, since we expect a pk, not a string as input.
I want to create appelationForm, because I have some custom validators I need to apply to validate the foreignKey input.
The limitations I see now, Is that I don't see how I can attach the validation errors from appelationForm to the ones of the main form so that they are displayed instead of the ones we would typically have from a foreignKey field.
To see the full example code:
https://github.com/quantumlicht/django-wine/blob/master/project/corewine/models.py
https://github.com/quantumlicht/django-wine/blob/master/project/corewine/forms.py
https://github.com/quantumlicht/django-wine/blob/master/project/corewine/views.py
What you should do is write a clean_appelation method on your WineForm, which comprehensively validates the input according to your criteria, i.e. either an existing Appelation id, or a new Appelation name, and raises the appropriate errors. Then in your view, you can assume the form data is valid and will work. This should give you something to start off with:
class WineForm(ModelForm):
...
appelation= CharField(widget=TextInput)
def clean_appelation(self):
data = self.cleaned_data['appelation']
if data.isdigit():
# assume it's an id, and validate as such
if not Appelation.objects.filter(pk=data):
raise forms.ValidationError('Invalid Appelation id')
else:
# assume it's a name
if ...:
raise forms.ValidationError('Invalid Appelation name')
return data
I have a read-only field in a django form that I sometimes want to edit.
I only want the right user with the right permissions to edit the field. In most cases the field is locked, but an admin could edit this.
Using the init function, I am able to make the field read-only or not, but not optionally read-only. I also tried passing an optional argument to StudentForm.init but that turned much more difficult that I expected.
Is there a proper way to do accomplish this?
models.py
class Student():
# is already assigned, but needs to be unique
# only privelidged user should change.
student_id = models.CharField(max_length=20, primary_key=True)
last_name = models.CharField(max_length=30)
first_name = models.CharField(max_length=30)
# ... other fields ...
forms.py
class StudentForm(forms.ModelForm):
class Meta:
model = Student
fields = ('student_id', 'last_name', 'first_name',
# ... other fields ...
def __init__(self, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance:
self.fields['student_id'].widget.attrs['readonly'] = True
views.py
def new_student_view(request):
form = StudentForm()
# Test for user privelige, and disable
form.fields['student_id'].widget.attrs['readonly'] = False
c = {'form':form}
return render_to_response('app/edit_student.html', c, context_instance=RequestContext(request))
Is that what you are looking for? By modifying your code a little bit:
forms.py
class StudentForm(forms.ModelForm):
READONLY_FIELDS = ('student_id', 'last_name')
class Meta:
model = Student
fields = ('student_id', 'last_name', 'first_name')
def __init__(self, readonly_form=False, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
if readonly_form:
for field in self.READONLY_FIELDS:
self.fields[field].widget.attrs['readonly'] = True
views.py
def new_student_view(request):
if request.user.is_staff:
form = StudentForm()
else:
form = StudentForm(readonly_form=True)
extra_context = {'form': form}
return render_to_response('forms_cases/edit_student.html', extra_context, context_instance=RequestContext(request))
So the thing is to check permissions on the views level, and then to pass argument to your form when it is initialized. Now if staff/admin is logged in, fields will be writeable. If not, only fields from class constant will be changed to read only.
It would be pretty easy to use the admin for any field editing and just render the student id in the page template.
I'm not sure if this answers your questions though.