2012-12-05 39 views
3

我有一个Django应用程序,用户提交付款订单。显然,安全性很重要。我想尽量减少必须编写的代码量,以避免引入任何安全漏洞并简化维护。使用ModelForm时为某些字段强制执行值

的模式很简单:

class Order(models.Model): 
    user = models.ForeignKey(User) 
    created = models.DateTimeField() 
    paid = models.DateTimeField(null=True, blank=True) 
    items = models.ManyToManyField(Item) 

我使用的是CreateView创造秩序的实例:

class OrderView(CreateView): 
    model = Order 
    form_class = OrderForm 

我要强制在这些情况下,某些字段的值。例如,我想将实例user字段设置为当前登录用户。我不希望用户可以更改此字段的值,因此我不希望它出现在表单中。因此,我使用自定义ModelForm从表单中删除这些字段:

class OrderForm(forms.ModelForm): 
    class Meta: 
     model = Order 
     # For security, we control exactly which fields are placed 
     # in the form, rather than excluding some: 
     fields = ('items',) 

现在我想新创建的单实例有user字段设置为当前登录的用户。我无法找到任何关于最佳这种方式的文档。

(A)我可以覆盖形式的save()方法保存之前修改的对象,但感觉这样的代码并不形式,它不知道关于user场什么的归属。我也无法访问此处的request,我需要确定当前用户。但它也可能是这样的:

class OrderForm(forms.ModelForm): 
    def save(self, commit=True): 
     instance = super(OrderForm, self).save(commit=False) 
     instance.user = get_request_magic().user 
     if commit: 
      instance.save() 
     return instance 

(B)我可以override the view's form_valid method保存对象与提交=假像this question基于类的版本。但是我不能直接调用超类的方法,因为它保存的对象没有办法禁用提交,所以我必须手动跳过生成form_valid这是很讨厌的一代。除此之外投诉,这看起来像到目前为止,我已经找到了最好的方法:

class OrderView(CreateView): 
    def form_valid(self, form): 
     self.object = form.save(commit=False) 
     self.object.user = self.request.user 
     self.object.save() 
     return super(ModelFormMixin, self).form_valid(form) 

(C)我可以写CreateView更换,增加了一个钩子,让物体在保存之前改变他们。但是,这感觉就像更多的样板和重复。

(D)我不能provide an initial value,因为没有表单域来放置它,所以它将被忽略。

还有其他想法吗?如果(B)是最好的选择,是否有任何方法可以手动指定我想调用哪个超类'form_valid方法?

回答

2

Django的用户Charettesanswered the question我:

您可以通过重写form_valid实现这一目标

class OrderCreateViewMixin(CreateView): 
    def form_valid(self, form): 
     form.instance.user = request.user 
     return super(OrderCreateViewMixin, self).form_valid(form) 

其中指出我朝着right part of the documentation

class AuthorCreate(CreateView): 
    form_class = AuthorForm 
    model = Author 

    def form_valid(self, form): 
     form.instance.created_by = self.request.user 
     return super(AuthorCreate, self).form_valid(form) 

这绝对是最简单和干净的答案到目前为止,我已经找到。它不需要以任何方式修改表单,虽然它直接访问它的有点难看的instance成员。但是,至少它是正式记录的,所以它不可能被打破。

0

这可能有多种方法。我这样做:

创建表单中的一个构造函数,接受请求:

def __init__(self, *args, **kwargs): 
     request = kwargs.pop('request', None) 
     super(OrderForm, self).__init__(*args, **kwargs) 
     self.request = request 

在创建后处理的形式,实例化,如下所示:

form = OrderForm(data=request.POST, request=request) 

现在,您的save()方法,您可以通过引用self.request.user访问请求上的用户,并可以在您的模型上相应地设置它。

+0

不幸的是,最后一部分不起作用。在'ModelForm'' save()'方法中,我们现在可以访问'request',但不能访问新的实例!这是由'save_instance'函数(不是方法)创建的,它调用'construct_instance'函数(不是方法),所以我必须重写这两个函数,并且它会再次变得难看。 – qris

0

我用CBV处理这种情况的方式是将未保存的模型实例传递给表单。这就是我已经做到了:

class OrderView(CreateView): 
    def get_form_kwargs(self): 
     self.object = Order(user=self.request.user) 
     return super(OrderView, self).get_form_kwargs() 

两个CreateViewUpdateView将增加instance到窗体kwargs,将其设置为self.object值。

除了你已经提到的,唯一的另一种方法是从CreateView所做的相同元素构建视图类,然后更改get和post方法以在那里填充self.object。我已经做了的时候我都需要很多建立在我的项目的看法:

class OrderView(SingleObjectTemplateResponseMixin, ModelFormMixin, ProcessFormView): 
    template_name_suffix = '_form' 

    def get(self, request, *args, **kwargs): 
     self.object = Order(user=request.user) 
     return super(OrderView, self).get(request, *args, **kwargs) 

    def post(self, request, *args, **kwargs): 
     self.object = Order(user=request.user) 
     return super(OrderView, self).post(request, *args, **kwargs) 

这里是一个更广义的版本被重用:https://gist.github.com/4439975

+0

感谢您的回答,不幸的是,它需要插入'CreateView'的未记录'object'属性,这可能会在将来更改并破坏此代码。 – qris

相关问题