How to populate a field in a django model - django

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',)

Related

Django: how to filter form field based off foreignkey AND many to many relationship?

Currently, when a user creates a task, they can assign it to all users. I only want them to be able to assign a task based on the members of the project. I feel like the concept I have right now works but I need to replace the ????. Task's assignee has a foreignkey relationship with the user_model. The user_model is also connected with members on a many to many relationship.
projects/models.py
class Project(models.Model):
name = models.CharField(max_length=200)
description = models.TextField()
members = models.ManyToManyField(USER_MODEL, related_name="projects")
tasks/models.py
class Task(models.Model):
name = models.CharField(max_length=200)
start_date = models.DateTimeField()
due_date = models.DateTimeField()
is_completed = models.BooleanField(default=False)
project = models.ForeignKey(
"projects.Project", related_name="tasks", on_delete=models.CASCADE
)
assignee = models.ForeignKey(
USER_MODEL, null=True, related_name="tasks", on_delete=models.SET_NULL
)
tasks/views.py
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
template_name = "tasks/create.html"
# fields = ["name", "start_date", "due_date", "project", "assignee"]
form_class = TaskForm
def get_form_kwargs(self):
kwargs = super(TaskCreateView, self).get_form_kwargs()
kwargs["user"] = self.request.user
kwargs["project_members"] = ??????????
return kwargs
tasks/forms.py
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ["name", "start_date", "due_date", "project", "assignee"]
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
project_members = kwargs.pop("project_members")
super(TaskForm, self).__init__(*args, **kwargs)
self.fields["project"].queryset = Project.objects.filter(members=user)
self.fields["assignee"].queryset = Project.objects.filter(
members=?????????
)
Update:
I followed SamSparx's suggestions and changed the URL paths so now TaskCreateView knows which project id. I updated my tasks/views to the following but I get a TypeError: "super(type, obj): obj must be an instance or subtype of type" and it points to the line: form = super(TaskForm, self).get_form(*args, **kwargs) Maybe it has something to do with having a get_form_kwargs and get_form function? I kept my existing features for the custom form such as when a user creates a task, they can only select projects they are associated with.
Views.py updated
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
template_name = "tasks/create.html"
form_class = TaskForm
def get_form_kwargs(self):
kwargs = super(TaskCreateView, self).get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
def get_form(self, *args, **kwargs):
form = super(TaskForm, self).get_form(*args, **kwargs)
form.fields["assignee"].queryset = Project.members.filter(
project_id=self.kwargs["project_id"]
)
def form_valid(self, form):
form.instance.project_id = Project.self.kwargs["project_id"]
return super(TaskCreateView, self).form_valid(form)
def get_success_url(self):
return reverse_lazy("list_projects")
I have also tried to update the forms.py with the following but get an error that .filter cannot be used on Many to Many relationships.
Updated forms.py
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ["name", "start_date", "due_date", "project", "assignee"]
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
super(TaskForm, self).__init__(*args, **kwargs)
self.fields["project"].queryset = Project.objects.filter(members=user)
self.fields["assignee"].queryset = Project.members.filter(
project_id=self.kwargs["project_id"]
)
Another thing I have tried is to go back to my first approach now that I have the url paths: tasks/create/(project_id)
Views.py
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
template_name = "tasks/create.html"
form_class = TaskForm
def get_form_kwargs(self):
kwargs = super(TaskCreateView, self).get_form_kwargs()
kwargs["user"] = self.request.user
kwargs["project_id"] = Project.objects.all()[0].members.name
# prints to auth.User.none
return kwargs
I feel like if the kwargs["project_id"] line can be changed to getting list of members of whatever project with the ID in the URL, then this should solve it
Forms.py
class TaskForm(ModelForm):
class Meta:
model = Task
fields = ["name", "start_date", "due_date", "project", "assignee"]
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
project_id = kwargs.pop("project_id")
super(TaskForm, self).__init__(*args, **kwargs)
self.fields["project"].queryset = Project.objects.filter(members=user)
self.fields["assignee"].queryset = Project.objects.filter(
members=project_id
)
The problem here is that your task doesn't know what members are relevant to include as assignees until you have chosen the project the task belongs to, and both project and assignee are chosen in the same form, so Django doeesn't know who is relevant yet.
The easiest way to handle this is to ensure the call to create a task is associated with the project it is going to be for - eg,
Update your URLs to handle the project ID
Path('create-task/<int:project_id>', TaskCreateView.as_view(), name='create_task')
Update your view
class TaskCreateView(LoginRequiredMixin, CreateView):
model = Task
template_name = "tasks/create.html"
# fields = ["name", "start_date", "due_date", "assignee"]
#NB: I have remove project from the field list, you may need to do the same in your form as it is handled elsewhere
form_class = TaskForm
def get_form(self, *args, **kwargs):
form = super(TaskCreateView, self).get_form(*args, **kwargs)
form.fields['assignee'].queryset = Project.members.filter(project_id = self.kwargs['project_id'])
Return form
def form_valid(self, form):
form.instance.project_id = project.self.kwargs['project_id']
return super(TaskCreateView, self).form_valid(form)
Add links
Create Task for this project
This will create a link on the project details page, or underneath the project in a listview to 'create task for this project', carrying the project informaton for the view via the URL. Otherwise you will have to get into some rather more complex ajax calls that populate the potential assignees list based on the selection within the project dropdown in a dynamic fashion

copy values of multiple ModelMutipleChoiceField into one after Post using MPTT

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()

Django How to override a child form in inlineformset_factory

I'm trying to override concept queryset in my child form, to get a custom list concepts based on the area got from request.POST, here is my list of concepts, which i need to filter based on the POST request, this lists is a fk of my child form (InvoiceDetail). is it possible to have these filters?
after doing some test when I pass the initial data as the documentation says initial=['concept'=queryset_as_dict], it always returns all the concepts, but i print the same in the view and its ok the filter, but is not ok when i render in template, so I was reading that I need to use some BaseInlineFormset. so when I test I obtained different errors:
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']
'InvoiceDetailFormFormSet' object has no attribute 'fields'
so here is my code:
models.py
class ConceptDetail(CreateUpdateMixin): # here, is custom list if area='default' only returns 10 rows.
name = models.CharField(max_length=150)
area = models.ForeignKey('procedure.Area')
class Invoice(ClusterableModel, CreateUpdateMixin): # parentForm
invoice = models.SlugField(max_length=15)
class InvoiceDetail(CreateUpdateMixin): # childForm
tax = models.FloatField()
concept = models.ForeignKey(ConceptDetail, null=True, blank=True) # fk to override using custom queryset
invoice = models.ForeignKey('Invoice', null=True, blank=True)
views.py
class CreateInvoiceProcedureView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
template_name = 'invoice/invoice_form.html'
model = Invoice
permission_required = 'invoice.can_check_invoice'
def post(self, request, *args, **kwargs):
self.object = None
form = InvoiceForm(request=request)
# initial initial=[{'tax': 16, }] removed
invoice_detail_form = InvoiceDetailFormSet(request.POST, instance=Invoice,
request=request)
return self.render_to_response(
self.get_context_data(
form=form,
invoice_detail_form=invoice_detail_form
)
)
forms.py
class BaseFormSetInvoice(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
# call first to retrieve kwargs values, when the class is instantiated
self.request = kwargs.pop("request")
super(BaseFormSetInvoice, self).__init__(*args, **kwargs)
self.queryset.concept = ConceptDetail.objects.filter(
Q(area__name=self.request.POST.get('area')) | Q(area__name='default')
)
class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoice
fields = ('invoice',)
class InvoiceDetailForm(forms.ModelForm):
class Meta:
model = InvoiceDetail
fields = ('concept',)
InvoiceDetailFormSet = inlineformset_factory(Invoice, InvoiceDetail,
formset=BaseFormSetInvoice,
form=InvoiceDetailForm,
extra=1)
How can i fix it?, what do i need to read to solve this problem, I tried to debug the process, i didn't find answers.
i try to do this:
def FooForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['concept'].queryset = ConceptDetail.objects.filter(area__name='default')
In a inlineformset_factory how can do it?.
After a lot of tests, my solution is override the formset before to rendering, using get_context_data.
def get_context_data(self, **kwargs):
context = super(CreateInvoiceProcedureView, self).get_context_data(**kwargs)
for form in context['invoice_detail_form']:
form.fields['concept'].queryset = ConceptDetail.objects.filter(area__name=self.request.POST.get('area'))
return context

Can't seem to exclude a field in a django form

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.

Django - Model field keeps the same value!

When I assign a value to an variable of a Field object, why when I reload the ModelForm isn't reassigned to default?
File
class CustomFile(ImageFile, FieldFile):
def save(self, name, content, save = True):
if self.field.override_name:
self(CustomFile, self).save(self.field.override_name, content, save = save)
else:
self(CustomFile, self).save(generate_name(self.instance, name), content, save = save)
Field
class CustomImageField(ImageField):
attr_class = CustomFile
def __init__(self, overrided_name, *args, **kwargs):
self.overrided_name = overrided_name
super(CustomImageField, self).__init__(*args, **kwargs)
Model
class Test(models.Model):
email = models.EmailField()
file = CustomImageField()
AdminForm
class TestForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs)
self.old_instance = self.instance
Admin
class TestAdmin(Test):
form = TestForm
def save_model(self, request, obj, form, change):
if form.old_instance:
form.old_instance.file.delete(save = True)
form.old_instance.file.field.override_name = form.old_instance.name
obj.save()
admin.site.register(Test, TestAdmin)
My problem is that every image I will upload will have the same name, until I restart the server!..
Why the object doesn't change?! In particular the Field object... when I trace it will result the same object .
I've solved it like so:
class CustomFile(ImageFile, FieldFile):
def save(self, name, content, save = True):
if self.field.override_name:
self(CustomFile, self).save(self.field.override_name, content, save = save)
self.field.override_name = None
else:
self(CustomFile, self).save(generate_name(self.instance, name), content, save = save)
I'm using Django 1.2.6, Python 2.6 and Windows!
I am just guessing, but isnt this part of code wrong?
form = TestForm
Should not it be creating an instance instead of referencing to class?
form = TestForm()
or same thing here?
attr_class = CustomFile - > attr_class = CustomFile()