I have a Django project in which I have a view subclassed from the Django CreateView class. This view is used to upload a file to the server, and uses an UploadedFile model which I have created. The UploadedFile also needs to be associated with a project, which is stored as a ForeignKey called project in the UploadedFile model.
The project id is passed in as part of the URL: (r'^projects/(?P<proj_key>\d+)/$', UploadedFileCreateView.as_view(), {}, 'upload-new')
Because project is not really a form field, I know I need to exclude it using a ModelForm; however, even after I have done so, django never enters the form_valid method (if I put a logging call in it, it will never be written to the log, though logging works fine). I'm guessing that the ForeignKey is the culprit because as far as I can tell it worked before I added that in. I don't understand why django doesn't consider the form to be valid even after I excluded project.
Here is my model definition:
class Project(models.Model):
"""This is a project that is owned by a user and contains many UploadedFiles."""
name = models.CharField(max_length=200)
class UploadedFile(models.Model):
"""This represents a file that has been uploaded to the server."""
STATE_UPLOADED = 0
STATE_ANNOTATED = 1
STATE_PROCESSING = 2
STATE_PROCESSED = 4
STATES = (
(STATE_UPLOADED, "Uploaded"),
(STATE_ANNOTATED, "Annotated"),
(STATE_PROCESSING, "Processing"),
(STATE_PROCESSED, "Processed"),
)
status = models.SmallIntegerField(choices=STATES,
default=0, blank=True, null=True)
file = models.FileField(upload_to=settings.XML_ROOT)
project = models.ForeignKey(Project)
def __unicode__(self):
return self.file.name
def name(self):
return os.path.basename(self.file.name)
def save(self, *args, **kwargs):
if not self.status:
self.status = self.STATE_UPLOADED
super(UploadedFile, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
os.remove(self.file.path)
self.file.delete(False)
super(UploadedFile, self).delete(*args, **kwargs)
class UploadedFileForm(forms.ModelForm):
class Meta:
model = UploadedFile
excludes = ('project',)
Here is my view definition:
class UploadedFileCreateView(CreateView):
model = UploadedFile
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.project_id = self.kwargs['proj_key']
self.object.save()
f = self.request.FILES.get('file')
data = [{'name': f.name,
'url': settings.MEDIA_URL + "files/" + f.name.replace(" ", "_"),
'project': self.object.project.get().pk,
'delete_url': reverse('fileupload:upload-delete',
args=[self.object.id]),
'delete_type': "DELETE"}]
response = JSONResponse(data, {}, response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return super(UploadedFileCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(UploadedFileCreateView, self).get_context_data(**kwargs)
return context
I see two likely problems:
1) The form keyword is exclude, not excludes.
Generally the recommendation is to favor explicitly listing fields to be included, so you don't accidentally expose any fields you might later add, but exclude will work.
2) You're not actually using your custom form class in the view. Set the form_class attribute to UploadedFileForm.
Related
I've been struggling with this issue all day and hope someone can help.
I have all my hierarchies classified by category in the same table.
during the form creation, I want to separate each hierarchy by category and render it using a ModelMutipleChoiceField his way not all hierarchies are displayed together.
The problem comes when the form is submitted, as I need to go through each ModelMutipleChoiceField field and get the selected values and copy these to the model field before saving the form. however, I am not able to iterate through the ModelMutipleChoiceField and get the selected values. I also don't know how to set these values on the ModelField
NOTE: The number of hierarchies can vary.
here is my code:
I'm using Django MPTT and create my hierarchy structure using 2 models.
one is the category(Hierarchy) and the other is the nodes of the hierarchy (HierarchyNode_MPTT).
Then I created a separate model that has ManyToManyField pointing to the HierarchyNode_MPTT.
Models.py
class Hierarchy(models.Model):
ID = kp.ObjectIDField()
name = kp.ObjectNameField()
ext_hierarchy = kp.ObjectTechnicalID()
seq_no = kp.SeqNoField(unique=True)
mptt_seq_no = models.PositiveIntegerField()
class HierarchyNode_MPTT(MPTTModel):
id = kp.ObjectIDField()
name = kp.ObjectNameField()
description = kp.ObjectDescriptionField()
ext_node_id = kp.ObjectShortNameField()
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
hierarchy = models.ForeignKey(Hierarchy, on_delete=models.CASCADE, null=True, blank=True, related_name='children')
class Configuration(models.Model):
uuid = kp.ObjectIDField()
name = kp.ObjectNameField()
description = kp.ObjectDescriptionField()
hierarchy_nodes = models.ManyToManyField(HierarchyNode_MPTT)
Then I created the form and implement the init method to automatically create as many hierarchies as I need.
form.py
class ConfigurationCreateForm(forms.ModelForm):
class Meta:
model = ForecastConfiguration
exclude = ['uuid', 'hierarchy_nodes']
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
hierarchies = Hierarchy.objects.all()
for hierarchy in hierarchies:
field_name = 'hierarchy_%s' % (hierarchy.mptt_seq_no,)
self.fields[field_name] = TreeNodeMultipleChoiceField(queryset=HierarchyNode_MPTT.objects.all().filter(hierarchy=hierarchy),label=hierarchy.name, required=True)
try:
self.initial[field_name] = HierarchyNode_MPTT.objects.root_node(tree_id=hierarchy.mptt_seq_no)
except IndexError:
self.initial[field_name] = ''
def copy_hierarchies(self, *args, **kwargs):
hierarchies = Hierarchy.objects.all()
choice_list = list()
for hierarchy in hierarchies:
field_name = 'hierarchy_%s' % (hierarchy.mptt_seq_no,)
selected_values = self.cleaned_data.get(field_name)
for selection in selected_values:
choice_list.append(selection)
self.initial['hierarchy_nodes'] = choice_list
Finally, the idea was to implement the post method on the View to loop over the created hierarchies and then assign the value to the model field called 'hierarchy_nodes'
view.py
class ConfigurationCreateView(CreateView):
model = Configuration
form_class = ConfigurationCreateForm
template_name = 'frontend/base/config_create.html'
def get(self, request, *args, **kwargs):
form = ConfigurationCreateForm(user=request.user)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
form.copy_hierarchies(*args, **kwargs)
if form.is_valid():
fcc_form = form.save(commit=True)
messages.add_message(self.request, messages.INFO, 'Your Forecast Configurations has been saved')
return redirect(reverse('planning_detail', kwargs={'uuid': self.fcc_form.uuid}))
else:
messages.add_message(self.request, messages.ERROR, 'Error when creating the Forecast Configuration')
return render(request, self.template_name, {'form': form})
As you can see I created a method in my form called copy_hierarchies which is where I was planning to copy the hierarchy values, this is the method where I'm having problems.
if there is an easier way to perform this using Javascript, I'm open to these options.
Thanks in advance.
I wasn't able to solve this using multi-choice field, however, the following is the solution for a ChoiceField (single selection)
1) Changed my view.py post method to save the object.
2) After the model is saved I loop over the request input filed and append the values to the created instance.
3) Save the instance.
4) delete my copy_hierarchies method in forms.py
here is the code snippet created in views.py
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
fcc = form.save()
for key in self.request.POST:
# check only the ones w/ 'hierarchy_#'
if key.startswith('hierarchy_'):
# get form field object
id = self.request.POST[key]
node = HierarchyNode_MPTT.objects.get(id=id)
# add to object instance
fcc.hierarchy_nodes.add(node)
fcc.save()
I have a Django project in which I have a view subclassed from the Django CreateView class. This view is used to upload a file to the server, and uses an UploadedFile model which I have created. The UploadedFile also needs to be associated with a project.
The project id is passed in as part of the URL: (r'^projects/(?P<proj_key>\d+)/$', UploadedFileCreateView.as_view(), {}, 'upload-new')
The problem is that I am not sure where the appropriate place is to associate this key with my model. Is there a method of CreateView or one of its ancestors that I should override that creates the model, or can this be done anywhere in my code in one of the methods I already override (this feels hacky though).
Furthermore, the project attribute of my UploadedFile is defined as a ForeignKey of type Project. How do I get the Project to associate with it?
Here is my model definition:
class Project(models.Model):
"""This is a project that is owned by a user and contains many UploadedFiles."""
name = models.CharField(max_length=200)
class UploadedFile(models.Model):
"""This represents a file that has been uploaded to the server."""
STATE_UPLOADED = 0
STATE_ANNOTATED = 1
STATE_PROCESSING = 2
STATE_PROCESSED = 4
STATES = (
(STATE_UPLOADED, "Uploaded"),
(STATE_ANNOTATED, "Annotated"),
(STATE_PROCESSING, "Processing"),
(STATE_PROCESSED, "Processed"),
)
status = models.SmallIntegerField(choices=STATES,
default=0, blank=True, null=True)
file = models.FileField(upload_to=settings.XML_ROOT)
project = models.ForeignKey(Project)
def __unicode__(self):
return self.file.name
def name(self):
return os.path.basename(self.file.name)
def save(self, *args, **kwargs):
if not self.status:
self.status = self.STATE_UPLOADED
super(UploadedFile, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
os.remove(self.file.path)
self.file.delete(False)
super(UploadedFile, self).delete(*args, **kwargs)
Here is my view definition:
class UploadedFileCreateView(CreateView):
model = UploadedFile
def form_valid(self, form):
logger.critical("Inside form_valid")
self.object = form.save()
f = self.request.FILES.get('file')
data = [{'name': f.name,
'url': settings.MEDIA_URL + "files/" + f.name.replace(" ", "_"),
'project': self.object.project.get().pk,
'delete_url': reverse('fileupload:upload-delete',
args=[self.object.id]),
'delete_type': "DELETE"}]
response = JSONResponse(data, {}, response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return super(UploadedFileCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(UploadedFileCreateView, self).get_context_data(**kwargs)
return context
You could do it right where you are calling form.save(). Just pass commit=False so that it won't save it to the db until you add the project id. For example:
self.object = form.save(commit=False)
self.object.project_id = self.kwargs['proj_key']
self.object.save()
Just make sure your form excludes the project field.
EDIT: to exclude the field, add an excludes variable to the form meta class:
class UploadedFileForm(forms.ModelForm):
class Meta:
model = UploadedFile
excludes = ('project',)
When you create a project in the app I'm working on it's possible to choose which users should become members of the project. However, I don't want the creator (to be admin) of the project to be on the list of users that you can choose to become members.
Either you are an admin OR a member, not both. Of course it's possible by not adding one self as a member, but I don't even want it to be possible.
How could this be done?
view:
def project_add(request):
if request.method == "POST":
form = ProjectForm(request.POST)
if form.is_valid():
form.instance.date_added = datetime.date.today()
form.instance.date_updated = datetime.date.today()
form.instance.added_by_user = request.user
form.save()
return redirect(project_list)
else:
form = ProjectForm()
return render(request, 'projects/add.html', {"form" : form})
project model:
class Project(models.Model):
...rest of the fields...
added_by_user = models.ForeignKey(User)
users = models.ManyToManyField(User, related_name='projects')
def __unicode__(self):
return self.name
def owned_by_user(self, user):
return self.added_by_user == user
the form:
class ProjectForm(ModelForm):
class Meta:
model = Project
exclude = ('date_added', 'date_updated', 'added_by_user')
...
To make it more clear I've provided an image below. I don't want "Nisse" to be possible to choose under "Users:" below (which is the members of the project), because Nisse is the user who is creating the project.
try this:
class ProjectForm(ModelForm):
...
users = forms.ModelMultipleChoiceField(queryset=User.objects.all())
# change the queryset for the modelform `users` field to exclude `instance.added_by_user`
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(self, *args, **kwargs)
if self.instance:
self.fields['users'].queryset = self.fields['users'].queryset.exclude(self.instance.added_by_user)
# custom clean method to not allow the `added_by_user`
def clean(self):
cleaned_data = super(ProjectForm, self).clean()
if self.instance.added_by_user in cleaned_data.get('users'):
raise ValidationError("OH NOES")
return cleaned_data
you may need to massage this a bit. its untested but you get the idea
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#overriding-the-clean-method
I have posts and attachments. The source of my problem is that I would like it to be possible to create attachments before creating the post they are attached to (because the user uses the fancy Ajax upload widget to upload the attachment before filling out the details of the post). The solution I have arrived at is to give each post a UUID. The UUID is generated when the post's modelform is instantiated (i.e., before creating the post). When attachments are uploaded, they are associated with this UUID. Before I pose my question, here is a sketch of the code in order to show more precisely what is going on:
# models.py
import uuid
from django.db import models
class Post(models.Model):
nonce = models.CharField(max_length=36)
class FooPost(Post):
body = models.TextField()
class UploadedFile(models.Model):
url = models.URLField(max_length=1024)
nonce = models.CharField(max_length=36)
#classmethod
def get_unique_nonce(cls):
while True:
nonce = str(uuid.uuid4())
if not cls.objects.filter(nonce=nonce).exists():
return nonce
class Attachment(UploadedFile):
post = models.ForeignKey(Post)
# views.py
class FooPostForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
self.initial.setdefault('uuid', UploadedFile.get_unique_uuid())
def save(self, *args, **kwargs):
obj = super(FooPostForm, self).save(*args, **kwargs)
if kwargs.get('commit', True):
for file in UploadedFile.objects.filter(nonce=obj.nonce)
Attachment(uploadedfile_ptr=file, post=obj).save_base(raw=True)
return obj
class Meta:
model = FooPost
def foo_post(request):
assert request.method == 'POST'
form = FooPostForm(request.POST)
if form.is_valid():
post = form.save()
# ...
This works well in my app, but I would like it to also work in the admin interface. How can I inline the attachments in the admin form for the post, even though they are linked indirectly by shared nonce instead of by a foreign key?
Provide a custom modelform for the inline:
class AttachmentForm(django.forms.ModelForm):
def save(self, *args, **kwargs):
obj = super(AttachmentForm, self).save(*args, **kwargs)
obj.nonce = self.instance.nonce
if kwargs.get('commit', True):
obj.save()
return obj
Exclude the nonce field from the inline:
class AttachmentInline(admin.TabularInline):
exclude = ['nonce']
model = Attachment
form = AttachmentForm
Come up with a new nonce for a new post:
class PostAdminForm(django.forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PostAdminForm, self).__init__(*args, **kwargs)
self.initial.setdefault('nonce', S3File.get_unique_nonce())
Use the inline:
# A particular kind of post
class FooPostAdmin(admin.ModelAdmin):
form = PostAdminForm
inlines = [AttachmentInline]
I am trying to save a modelform that represents a bank account but I keep getting a ValueError even though the form appears to validate. The models I have to use are:
class Person(models.Model):
name = models.CharField()
class Bank(models.Model):
bsb = models.CharField()
bank_name = models.CharField()
def __unicode__(self):
return '%s - %s', (self.bank_name, self.bsb)
def _get_list_item(self):
return self.id, self
list_item = property(-get_list_item)
class BankAccount(models.Model):
bank = models.ForignKey(Bank)
account_name = models.CharField()
account_number = models.CharField()
class PersonBankAcc(models.Model):
person = models.ForeignKey(Person)
The ModelForm for the personBankAcc;
def PersonBankAccForm(forms.ModelForm):
bank = forms.ChoiceField(widget=SelectWithPop)
class Meta:
model = PersonBankAcct
exclude = ['person']
def __init__(self, *args, **kwargs):
super(PersonBankAccForm, self).__init__(*args, **kwargs)
bank_choices = [bank.list_item for banks in Bank.objects.all()]
bank_choices.isert(0,('','------'))
self.fields['bank'].choices = bank_choices
The view is:
def editPersonBankAcc(request, personBankAcc_id=0):
personBankAcc = get_object_or_404(PersonBankAcc, pk=personBankAcc_id)
if request.method == 'POST':
form = PersonBankAccForm(request.POST, instance=personBankAcc )
if form.is_valid():
print 'form is valid'
form.save()
return HttpResponseRedirect('editPerson/' + personBankAcc.person.id +'/')
else:
form = PersonBankAccForm(instance=personBankAcc )
return render_to_response('editPersonBankAcc', {'form': form})
When I try to save the form I get the a VlaueError exception even though it gets passed the form.is_valid() check, the error I get is:
Cannot assign "u'26'": PersonBankAcc.bank must be a "bank" instance
I know the issue is arising because of the widget I am using in the PersonBankAccForm:
bank = forms.ChoiceField(widget=SelectWithPop)
because if I remove it it works. But all that does is gives me the ability to add a new bank to the database via a popup and then inserts it into the select list, similar to the admin popup forms. I have checked the database and the new bank is added. But it fails even if I don't change anything, if I call the form and submit it, I get the same error.
I don't understand why it does not fail at the is_valid check.
Any help would be greatly appreciated.
Thanks
Andrew
better yet, i don't think it really needs to be in the init function...
def PersonBankAccForm(forms.ModelForm):
bank = forms.ModelChoiceField(queryset=Bank.objects.all(),widget=SelectWithPop(),empty_label='-----')
class Meta:
model = EmpBankAcct
exclude = ['person']
def __init__(self, *args, **kwargs):
super(PersonBankAccForm, self).__init__(*args, **kwargs)