I have a little question regarding Forms / Views which don't use a Model object. I seem to have it set up almost the way it should, but I can't seem to figure out how to pass data around to initialise the fields in my edit form.
What I have to do is get data from a REST server which was developed using Delphi. So this django thingie won't be using the normal django ORM model thing. Currently I have it working so my app displays a list of departmets which it got using a REST call to the server. Each department has it's ID as a hyperlink.
My next step / thing I would like to do is display a form in which the user can edit some values for the selected department. Logically everything seems to be hooked up together the way it should (as far as I can see). Sadly ... for whatever reason ... I can't seem to pass along information about the clicked ID or even the selected object in my list to the detail view.
Would anyone be able to help me out ? This is what I have so far :
The urls.py :
# DelphiClient/urls.py
from django.conf.urls import patterns
from django.conf.urls import url
from . import views
urlpatterns = patterns("",
url(
regex=r"^Departments$",
view=views.DelphiDepartmentsListView.as_view(),
name="Departments"
),
url(
regex=r'^Department/(?P<pk>\d+)/$',
view=views.DepartmentFormView.as_view(),
name='department_update'
),
)
The views.py :
# DelphiClient/views.py
...
from .client import DelphiClient
from .forms import DepartmentForm
class DelphiDepartmentsListView(TemplateView):
template_name = 'DelphiDepartmentList.html'
def get_context_data(self, **kwargs):
client = DelphiClient()
departments = client.get_department()
context = super(DelphiDepartmentsListView, self).get_context_data(**kwargs)
context['departments'] = departments
#client.update_department(1, 'Update From Django')
return context
class DepartmentFormView(FormView):
template_name = 'DepartmentUpdate.html'
form_class = DepartmentForm
success_url = '/DelphiClient/Departments'
def get_initial(self, **kwargs):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(DepartmentFormView, self).get_initial(**kwargs)
# How can I get the ID passed along from the list view
# so I can get the correct object from my REST server and
# pass it along in the Initial ???
return initial
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
print "form.data {0}".format(form.data)
client = DelphiClient()
client.update_department(form.data["flddepartmentId"],form.data["flddepartmenet"])
return super(DepartmentFormView, self).form_valid(form)
The forms.py :
# DelphiClient/forms.py
from django import forms
from .client import DelphiClient
class DepartmentForm(forms.Form):
# How can I fill in the values for these fields using an object passed in
# thhrough Initial or the context?
flddepartmentId = forms.IntegerField(label="Department ID") #, value=1)
flddepartmenet = forms.CharField(label="New Description", max_length=100)
def update_department(self, *args, **kwargs):
#print "update_department"
#print self.data
#print self.data["flddepartmenet"]
client = DelphiClient()
client.update_department(self.data["flddepartmentId"],self.data["flddepartmenet"])
And the template for the form :
<h1>Update Department</h1>
<p>Update Department? {{ department.flddepartmentid }}</p>
<p>Something : {{ something }}</p>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<p><label for="id_flddepartmentId">Department ID:</label> <input id="id_flddepartmentId" name="flddepartmentId" type="number" value="1"></p>
<p><label for="id_flddepartmenet">New Description:</label> <input id="id_flddepartmenet" maxlength="100" name="flddepartmenet" type="text"></p>
<input type="submit" value="OK">
</form>
As you can see ... I'm close ... but no cigar yet :-) Since I'm completely new to Python / Django and have been learning on the go, I have no idea what I'm doing wrong or where I should look.
If anyone would be able to help or point me in the right direction it would be really appreciated.
The positional and name-based arguments are stored in self.args and self.kwargs respectively (see the docs on name based filtering). Therefore you can access the pk with self.kwargs['pk'].
I'm not sure that you should include flddepartmentId as an editable field in the form. It means that users could go to /Department/1/, but then enter flddepartmentId=2 when they submit the form. It might be better to remove the field from the form, then use the value from the URL when calling update_department.
client.update_department(self.kwargs['pk'],self.data["flddepartmenet"])
If you are sure that you want to include flddepartmentId in your form, then your get_initial method should look as follows:
def get_initial(self, **kwargs):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(DepartmentFormView, self).get_initial(**kwargs)
initial['flddepartmentId'] = self.kwargs['pk']
return initial
Related
I am just starting to work with Django and I have some problems with forms and dropping lists.
I have a model with two attributes, and I want to display one of the attributes in a dropping down list (this one will be unchangeable) and another one in a text field (this one will be changeable). Also, I have a submit button, so I want to change a second attribute in a text field and by pressing on the button. How can I do this? What would some examples be?
As you are starting to work with Django, you might or might not know about how Django handle forms.
In Django, forms can be handled in two ways:
User-created and managed forms (without any form class)
Class-managed forms (connected to Django models)
Documentation form Django Forms
Now let’s talk about the first type of forms (where you create your HTML form and manage the request sent to server):
These forms are simple to make and when there are only a few and are only suggested when you have a very small amount of inputs (I would say four or fewer inputs).
Here is a simple example of subscription of a newsletter with an email example.
<form id='sub-form' method="POST">
{% csrf_token %}
<div>
<input type="email" name="sub_email">
</div>
<input class="button" value="Subscribe" type="submit" id="subbutton">
</form>
So a very important thing to look at here is {% csrf_token %}, about which you can read more about here and about how it works and prevents cross-site request forgery. This token will be required when you make a request to Django server with any post request and data.
In this subscription form you see one <input> with name="sub_email". Take note of this as we will use this to get this value on the server as this is the key to its value, and then a simple Submit Button.
When you press Submit on a page let’s say url = "http://BASE_URL/home" you will receive a POST request on the view that handles that URL.
So now coming to the view.py, let’s say you only allow registered users to subscribe then the view will go something like this (assuming you are not expecting any other request from the home URL).
def home(request):
user=request.user
if request.method == "POST":
if user.is_authenticated:
email = request.POST['sub_email'] #Using name of input
#Logic to save this email
return HttpResponse("You are Subscribed",status=200)
else:
return HttpReposnse("You are not Authenticated",status=401)
else:
return render(request,"home.html")
Now as you are the expert of simple forms, let’s work with Django class-based forms.
These views are a little work when you have very few inputs, but they are a great help in manageability and when you have to work with large number of inputs.
You will request these Class Based Forms as in your question you are trying to send an instance of a model from your Models.py to a form to user.
I have a model of Posts that can be used for this example:
class Post(models.Model):
postTitle = models.CharField(max_length = 90,null=True)
subTitle = models.CharField(max_length = 160,null=True)
location = models.CharField(max_length = 3,default = 'IN',null=True)
Now according to your question, you are trying to let the user change one attribute, let’s say postTitle and for location you are not letting the user select one of the countries which is preselected and for your post.
Now we have to create a form for this. Forms in class based are created in Forms.py. If you don't have forms.py then you can create one right along models.py and views.py.
Now for the form, I would like to edit some existing data as you are saying one of the attributes (Fields) is fixed and another editable, but you get the value from the model.
class PostEditForm(ModelForm):
location = forms.CharField(label='Country ',widget=forms.Select(attrs={'class': 'Classes_HERE','placeholder':' Select a Country','disabled':'disabled'} ,choices=country_list),required=True)
class Meta:
model = Post
fields= ['postTitle','subTitle','location']
labels = {
'postTitle':'Title',
'subTitle':'Sub-Title',
}
widgets = {
'postTitle': forms.TextInput(attrs={'class': 'mention_class_here','placeholder':' Add Title'}),
'subTitle': forms.TextInput(attrs={'class': 'mention_class_here','placeholder':' Add Sub-Title'})
}
Attributes can be mentioned in forms fields the way I have mentioned them in the above example. I used disabled="disabled" to disable (not editable) location field and used forms.Select to make it drop down.
You might also see that I gave the location field a list to choose from. This is how you can create a list of your items. It's been quite some time when I wrote this, so there might be errors or it may not work for you, so just make sure you are referring to the current documentation and searching Stack Overflow for answers.
country_list = [
('', 'Select a Country'),
("AF", "Afghanistan"),
("AX", "Aland Islands"),
("AL", "Albania"),
("DZ", "Algeria"),
("AS", "American Samoa"),
("AD", "Andorra"),
("AO", "Angola"),
("AI", "Anguilla"),
("AQ", "Antarctica"),
("AG", "Antigua And Barbuda"),
("AR", "Argentina"),
("AM", "Armenia"),
("AW", "Aruba"),
.
.
.
Now this form can be passed as context in a view to an HTML page.
def editPost(request,post_id):
user=request.user
post = get_object_or_404(Post,id=post_id) #Getting the instance of Post
if user.is_authenticated:
formPost = PostEditForm(request.POST or None,instance=post)
if request.method=='POST':
if formPost.is_valid():
savedPost=formPost.save()
else:
return render(request,'postEdit.html',{'formPost':formPost})
else:
return HttpResponse("Not Authorized",status:401)
Now your HTML file postEdit.html should look something like this:
<form id="post-form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div>
{{formPost}}
</div>
</form>
That is it and adding a submit button in the same form, you can now edit your instance of post that you passed along with this {{formPost}}. Combine your logic wherever you think needs a change to fit in what you want to do.
By no means I am saying all this code is in working condition, but it is shown only to illustrate the flow and working.
I'm using Django 2.1.
I'm having a problem with a CreateView because I need to redirect to the update url, but that url contains one argument that is created manually after verifying that the form is valid.
This is the view code:
class ProjectCreateInvestmentCampaignView(LoginRequiredMixin, SuccessMessageMixin, generic.CreateView):
template_name = 'webplatform/project_edit_investment_campaign.html'
model = InvestmentCampaign
form_class = CreateInvestmentCampaignForm
success_message = 'Investment campaign created!'
def get_success_url(self):
return reverse_lazy('project-update-investment-campaign',
args=(self.kwargs['pk'], self.object.campaign.pk, self.object.pk))
def form_valid(self, form):
project = Project.objects.get(pk=self.kwargs['pk'])
form.instance.investment_type = "A"
form.instance.contract_type = "CI"
form.instance.history_change_reason = 'Investment campaign created'
valid = super(ProjectCreateInvestmentCampaignView, self).form_valid(form)
if valid:
campaign = CampaignBase.objects.create(project=project, )
form.instance.campaign = campaign
form.instance.campaign.project = project
form.instance.campaign.creation_date = timezone.now()
form.save()
return valid
As you can see, on the form_valid I validate first the form, and then I create the object campaign and assign all the related data. This is working fine.
The problem came when I changed the get_success_url to fit my use case, that is redirecting to the update view.
I debugged and saw that at the moment I create the variable valid on the form_valid, it checks the success url, and that triggers me the following error:
Exception Type: AttributeError
Exception Value:
'NoneType' object has no attribute 'pk'
Exception Location: /Volumes/Archivos/work/i4b/webplatform/views/investor_campaign_views.py in get_success_url, line 25
I asume that the error is because the campaign is not created yet so it's trying to get the pk from a non existing object.
The thing is that I cannot create the campaign if the form is not validated, but I need the campaign to make the url working (that url is working as it is on the UpdateView that I already have).
It will only invoke get_success_url after form_valid. So it's up to form_valid to create and save the objects needed. If it's valid for them not to be created, you need a different approach. Maybe initialize (say) self.campaign_pk = 0, update it if a campaign can be created with the pk of the campaign object, and let the next view sort out what to do when pk==0. Or,
...
args=(self.kwargs['pk'],
self.object.campaign.pk if self.object.campaign else 0,
self.object.pk))
(I don't fully follow your code so I might be barking up the wrong tree here)
It may be that you don't want CreateView but FormView, which doesn't handle object creation for you, so you may find greater flexibility over how to process a valid form that nevertheless cannot be fully honoured all the time. Or even, just a plain old function-based view, in which you can process two or more forms and be far more able to decide on conditions that constitute non-validity even after all the forms have technically validated.
This is a function-based view structure I have used where I have two forms to process, and a fairly long but boring set of operations to do after BOTH forms validate:
def receive_view( request):
# let's put form instantiation in one place not two, and reverse the usual test. This
# makes for a much nicer layout with actions not sandwiched by "boilerplate"
# note any([ ]) forces invocation of both .is_valid() methods
# so errors in second form get shown even in presence of errors in first
args = [request.POST, ] if request.method == "POST" else []
batchform = CreateUncWaferBatchForm( *args, layout=CreateUncWaferBatchLayout )
po_form = CreateUncWaferPOForm( *args, layout = CreateUncWaferPOLayout, prefix='po')
if request.method != "POST" or any(
[ not batchform.is_valid(), not po_form.is_valid() ]):
return render(request, 'wafers/receive_uncoated.html', # can get this out of the way at the top
{'batchform': batchform,
'po_form': po_form,
})
#it's a POST, everything is valid, do the work
...
return redirect('appname:viewname', ...)
For me, get_success_url was not invoked as the form was not valid (was invalid) and I didn't know. You can override form_invalid(self, form) to control the behavior.
Also, consider this block of code to show any errors in your template
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<b>{{ field }}</b>: {{ error }}
{% endfor %}
{% endfor %}
</div>
{% endif %}
So basically i have a complicated scenario. I am current using Django to build a website and i have current made two apps. Both apps have almost identical fields. The field I would want to focus on though is the Information field(which they both have and which i have auto generated with the help of the Wikipedia model)
So the case here is that I want to create an if and else statement in the html in such a way that if the page i am hyperlinking to exists it would go to the link dealing with DetailView but if it doesnt exist I would redirected to the Create View
I should also note that the two apps have their names linked with the help of the foreign key but when i try to open information links using the same name , they gave me different pks
I dont feel like i explained my problem well enough but I hope someone can understand what i mean
UPDATE
ok I create the get function using
def get(self, request, *args, **kwargs):
try:
self.object = self.get_object()
except Http404:
return redirect('/create/')
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
but i dont know how to use the CreateView fuction i created instead of the link i put
This is the Detail View Html
{%extends "home.html"%}
{%block head_title%} {{block.super}}{%endblock head_title%}
{% block content%}
<!-- verify authentication -->
{% if request.user.is_authenticated%}
<h3>{{object.title}}</h3><br/>
{% endif %}
<ul type="disc">
<div class="container">
<li><b>Artist: </b>{{object.Summary}}</li>
<li><b>Genre: </b>{{object.Genre}}</li>
<li><b>Bio: </b><br>{{object.Bio}}</li>
EDIT
</div>
</ul>
{%endif%}
{% endblock %}
This is my model
from django.db import models
from django.conf import settings
from Blog.models import MainPage
from django.urls.base import reverse
from Blog.Retrieve import retriever
from django.db.models.signals import pre_save,post_save
import InfoPedia
class InfoPedia(models.Model):
user =models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
Name =models.ForeignKey(MainPage,on_delete=models.CASCADE)
Location =models.CharField(max_length= 50,null=True,blank=True)
Information =models.TextField(null=True,blank=True)
TrackListing=models.TextField(null=True,blank=True)
Published=models.BooleanField(default=True)
Timestamp=models.DateTimeField(auto_now=True)
Updated=models.DateTimeField(auto_now=True)
def get_absolute_url(self):
# return f"/Blog/{self.slug}"
return reverse('InfoPedia:DetailView', kwargs={"pk":self.pk})
class Meta:
ordering=["-Updated","-Timestamp"] #orranges in order of updated
def get_tracklist(self):
return self.TrackListing.split(",")
def Information_create_pre_save( instance, sender, **kwargs):
instance.Information=retriever(instance.Name)
def rl_post_save_reciever(sender, instance,created,*args,**kwargs):
print("saved")
print(instance.Timestamp)
pre_save.connect(Information_create_pre_save, sender=InfoPedia)
post_save.connect(rl_post_save_reciever, sender=InfoPedia)
An alternative - rather than checking the if/else in the HTML, just make all the links to the DetailView URL.
Then, in the get() handler for the DetailView, you perform a queryset lookup for the object. If no object is found, then instead of displaying the DetailView HTML, return to the user a 302 redirect (i.e. a temporary redirect) to the CreateView for that object. So all your if/else logic is in the view function or class, instead of HTML.
I have some fields in page disabled as for example:(using jinja2 templating system)
<html>
<body>
<form action="" method=POST>
{{ form.name(disabled=True) }}
{{ form.title }}
-- submit button --
</form>
</body>
</html>
Field is disabled in the form as expected.
In my views.py: On doing validate_on_submit() on form submit, it fails with validation error on 'name' field which is disabled. I was hoping that validation ignores disabled field. Is it the right behaviour? If so, can you please let know how to handle such a case?
Updated:
class TeamForm(wtf.Form):
name = wtf.TextField("Team Name", validators=[validators.Required()])
title = wtf.TextField("Title", validators=[validators.Required()])
This is actually an interesting problem, and the way WTForms solves it is intentionally something that requires explicitness, because it has to do with security and not allowing users to fake input.
So the intent is, that "managers" cannot edit the name, while "admins" can.
At first glance this seems obvious, just disable the field in HTML, and write your view like this:
def edit_team():
form = TeamForm(request.POST, obj=team)
if request.POST and form.validate():
form.populate_obj(team) # <-- This is the dangerous part here
return redirect('/teams')
return render('edit_team.html')
As written, this is a major security risk, because the disabled property in HTML forms is client-side only. Anyone with an HTML inspector (ie FireBug, webkit document inspector, etc) can remove this property, or someone could simply make a request like so:
POST /edit_team/7 HTTP/1.0
Content-Type: application/x-urlencoded
team=EVILTEAMNAME&title=foo
The issue then is of course, how do we gate this properly on the server-side, corresponding to the appropriate way of doing this? The correct approach with WTForms is to not have the field in the first place. There's a few ways to do this, one is to use form composition and have e.g. ManagerTeamForm and AdminTeamForm (sometimes this is better) but other times it's easier to use del to remove specific fields.
So here's how you would write your view, and not have the validation issues:
def edit_team():
form = TeamForm(request.POST, obj=team)
if user.role == 'manager':
del form.name
if request.POST and form.validate():
form.populate_obj(team)
return redirect('/teams')
return render('edit_team.html')
And a quick modification to the template:
<html>
<body>
<form action="" method=POST>
{% if 'name' in form %}
{{ form.name() }}
{% else %}
{{ team.name|e }}
{% endif %}
{{ form.title }}
-- submit button --
</form>
</body>
</html>
Some pieces of reference for wtforms best-practices:
WTForms 'Solving Specific Problems'
Dangers of Using forms as a backing store (WTForms google group) Post 1 / Post 2
StackOverflow: WTForms 'readonly' attribute
You need to make the name field optional when defining the form.
name = wtf.TextField("Team Name", validators=[validators.Optional()])
Then in your views, pass a variable called "role" and set it to either manager or admin depending on the user.
<form action="" method=POST>
{% if role == 'manager' % }
{{ form.name(disabled=True) }}
{% else % }
{{ form.name() }}
{{ form.title }}
-- submit button --
</form>
I defined my own validator for this problem:
from wtforms.validators import Optional
class OptionalIfDisabled(Optional):
def __call__(self, form, field):
if field.render_kw is not None and field.render_kw.get('disabled', False):
field.flags.disabled = True
super(OptionalIfDisabled, self).__call__(form, field)
And then I defined a new base for my forms:
from wtforms.form import Form
class BaseForm(Form):
def populate_obj(self, obj):
for name, field in self._fields.items():
if not field.flags.disabled:
field.populate_obj(obj, name)
Now every form can extend the BaseForm and disable fields like this:
from wtforms.fields import StringField, SubmitField
class TeamForm(BaseForm):
team = StringField(label='Team Name',
validators=[OptionalIfDisabled(), InputRequired()]
submit = SubmitField(label='Submit')
def __init__(self, *args, **kwargs):
super(TeamForm, self).__init__(*args, **kwargs)
# disable the fields if you want to
if some_condition:
self.team.render_kw = {'disabled': True}
After validation of the TeamForm, you can use populate_obj to copy the enabled form data in any object. It will ignore the disabled fields.
Create a custom validator
from wtforms.validators import Optional
class DisabledValidator(Optional):
"""
do nothing
"""
pass
Let's create a custom rule basing on the form.rule
from flask_admin.form.rules import Field
class EasyCustomFieldRule(Field):
def __init__(self, field_name, render_field='lib.render_field', field_args={}):
super(self.__class__, self).__init__(field_name, render_field)
self.extra_field_args = field_args
def __call__(self, form, form_opts=None, field_args={}):
field = getattr(form, self.field_name)
if self.extra_field_args.get('disabled'):
field.validators.append(DisabledValidator())
field_args.update(self.extra_field_args)
return super(self.__class__, self).__call__(form, form_opts, field_args)
Override write some functions of wtforms.form
from wtforms.form import Form
from wtforms.compat import iteritems
class BaseForm(Form):
"""
重写部分方法,以适应disabled的Field
"""
def validate(self):
"""
Validates the form by calling `validate` on each field, passing any
extra `Form.validate_<fieldname>` validators to the field validator.
"""
extra = {}
for name in self._fields:
inline = getattr(self.__class__, 'validate_%s' % name, None)
if inline is not None:
extra[name] = [inline]
return self.validate_(extra)
def validate_(self, extra_validators=None):
self._errors = None
success = True
for name, field in iteritems(self._fields):
is_disabled = False
for v in field.validators:
if isinstance(v, DisabledValidator):
field.flags.disabled = True
is_disabled = True
break
if is_disabled:
continue
if extra_validators is not None and name in extra_validators:
extra = extra_validators[name]
else:
extra = tuple()
if not field.validate(self, extra):
success = False
return success
def populate_obj(self, obj):
for name, field in self._fields.items():
if not field.flags.disabled:
field.populate_obj(obj, name)
set the form_base_class in your ModelView, and set the form_edit_rules or the form_create_rules with EasyCustomFieldRule
from flask_admin.contrib.sqla import ModelView
class MyTestModelView(ModelView):
...
form_base_class = BaseForm
...
form_edit_rules = (
EasyCustomFieldRule('column0', field_args={'disabled': True}),
'column1', 'column2'
)
Just testing...
I went to all the documentation, also I went to the IRC channel (BTW a great community) and they told me that is not possible to create a model and limit choices in a field where the 'current user' is in a ForeignKey.
I will try to explain this with an example:
class Project(models.Model):
name = models.CharField(max_length=100)
employees = models.ManyToManyField(Profile, limit_choices_to={'active': '1'})
class TimeWorked(models.Model):
project = models.ForeignKey(Project, limit_choices_to={'user': user})
hours = models.PositiveIntegerField()
Of course that code doesn't work because there is no 'user' object, but that was my idea and I was trying to send the object 'user' to the model to just limit the choices where the current user has projects, I don't want to see projects where I'm not in.
Thank you very much if you can help me or give me any advice, I don't want to you write all the app, just a tip how to deal with that. I have 2 days with this in my head and I can't figure it out :(
UPDATE: The solution is here: http://collingrady.wordpress.com/2008/07/24/useful-form-tricks-in-django/ sending request.user to a model.
This limiting of choices to current user is a kind of validation that needs to happen dynamically in the request cycle, not in the static Model definition.
In other words: at the point where you are creating an instance of this model you will be in a View and at that point you will have access to the current user and can limit the choices.
Then you just need a custom ModelForm to pass in the request.user to, see the example here:
http://collingrady.wordpress.com/2008/07/24/useful-form-tricks-in-django/
from datetime import datetime, timedelta
from django import forms
from mysite.models import Project, TimeWorked
class TimeWorkedForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(user=user)
class Meta:
model = TimeWorked
then in your view:
def time_worked(request):
form = TimeWorkedForm(request.user, request.POST or None)
if form.is_valid():
obj = form.save()
# redirect somewhere
return render_to_response('time_worked.html', {'form': form})
Model itself doesn't know anything about current user but you can give this user in a view to the form which operates models objects (and in form reset choices for necessary field).
If you need this on admin site - you can try raw_id_admin along with django-granular-permissions (http://code.google.com/p/django-granular-permissions/ but I couldn't rapidly get it working on my django but it seems to be fresh enough for 1.0 so...).
At last, if you heavily need a selectbox in admin - then you'll need to hack django.contrib.admin itself.
Using class-based generic Views in Django 1.8.x / Python 2.7.x, here is what my colleagues and I came up with:
In models.py:
# ...
class Proposal(models.Model):
# ...
# Soft foreign key reference to customer
customer_id = models.PositiveIntegerField()
# ...
In forms.py:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.forms import ModelForm, ChoiceField, Select
from django import forms
from django.forms.utils import ErrorList
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from .models import Proposal
from account.models import User
from customers.models import customer
def get_customers_by_user(curUser=None):
customerSet = None
# Users with userType '1' or '2' are superusers; they should be able to see
# all the customers regardless. Users with userType '3' or '4' are limited
# users; they should only be able to see the customers associated with them
# in the customized user admin.
#
# (I know, that's probably a terrible system, but it's one that I
# inherited, and am keeping for now.)
if curUser and (curUser.userType in ['1', '2']):
customerSet = customer.objects.all().order_by('company_name')
elif curUser:
customerSet = curUser.customers.all().order_by('company_name')
else:
customerSet = customer.objects.all().order_by('company_name')
return customerSet
def get_customer_choices(customerSet):
retVal = []
for customer in customerSet:
retVal.append((customer.customer_number, '%d: %s' % (customer.customer_number, customer.company_name)))
return tuple(retVal)
class CustomerFilterTestForm(ModelForm):
class Meta:
model = Proposal
fields = ['customer_id']
def __init__(self, user=None, *args, **kwargs):
super(CustomerFilterTestForm, self).__init__(*args, **kwargs)
self.fields['customer_id'].widget = Select(choices=get_customer_choices(get_customers_by_user(user)))
# ...
In views.py:
# ...
class CustomerFilterTestView(generic.UpdateView):
model = Proposal
form_class = CustomerFilterTestForm
template_name = 'proposals/customer_filter_test.html'
context_object_name = 'my_context'
success_url = "/proposals/"
def get_form_kwargs(self):
kwargs = super(CustomerFilterTestView, self).get_form_kwargs()
kwargs.update({
'user': self.request.user,
})
return kwargs
In templates/proposals/customer_filter_test.html:
{% extends "base/base.html" %}
{% block title_block %}
<title>Customer Filter Test</title>
{% endblock title_block %}
{% block header_add %}
<style>
label {
min-width: 300px;
}
</style>
{% endblock header_add %}
{% block content_body %}
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Save" class="btn btn-default" />
</form>
{% endblock content_body %}
I'm not sure that I fully understand exactly what you want to do, but I think that there's a good chance that you'll get at least part the way there using a custom Manager. In particular, don't try to define your models with restrictions to the current user, but create a manager that only returns objects that match the current user.
Hmmm, I don't fully understand your question. But if you can't do it when you declare the model maybe you can achieve the same thing with overriding methods of the class of objects where you "send" the user object, maybe start with the constructor.
Use threadlocals if you want to get current user that edits this model. Threadlocals middleware puts current user into process-wide variable. Take this middleware
from threading import local
_thread_locals = local()
def get_current_user():
return getattr(getattr(_thread_locals, 'user', None),'id',None)
class ThreadLocals(object):
"""Middleware that gets various objects from the
request object and saves them in thread local storage."""
def process_request(self, request):
_thread_locals.user = getattr(request, 'user', None)
Check the documentation on how to use middleware classes. Then anywhere in code you can call
user = threadlocals.get_current_user