Django - 基于queryset生成表单

时间:2014-07-15 02:16:20

标签: python django forms

我有一个问题,我会问两种方式:短和&通用,因此未来几代StackOverflow读卡器将受益,而Long&详细,所以我可以在不搞砸的情况下完成我的工作。

简短&通用版本:

如何让Django生成一个类似于表格的表格,其中表格中的某些信息来自数据库,用户填写其余信息?在表单提交时,表中的每一行都应该成为数据库中的记录(当然,在验证之后)。

最干净的方法是什么?这种互动方式是什么?这是一种无比的称呼方式?

示例表格

|=============================================================|
| QueriedValue | CalculatedValue | User_data | More_User_data |
|_____________________________________________________________|
|     Foo 1    |    Bar 1        |           |                |
|     Foo 2    |    Bar 2        |           |                |
|     Foo 3    |    Bar 3        |           |                |
...            ...               ...         ...              |
|     Foo n    |    Bar n        |           |                |
|=============================================================|

    ++++++++++
    | Submit |
    ++++++++++

产生的数据库记录

     TimeStamp + fk_Foo = natural primary key for this table
    ________________
   /                \
|===========================================================|
|   TimeStamp  |    fk_Foo    | User_data | More_User_data  |
|___________________________________________________________|
| submit_time  |     Foo 1    |  Datum 1  | AnotherDatum 1  |
| submit_time  |     Foo 2    |  Datum 2  | AnotherDatum 2  |
| submit_time  |     Foo 3    |  Datum 3  | AnotherDatum 3  |
|...           ...            ...              ...          |
| submit_time  |     Foo n    |  Datum n  | AnotherDatum n  |
|===========================================================|

长版

我正在编写一个网络应用程序来跟踪我公司的气瓶使用情况。我们的建筑物里有一堆煤气管道,我们需要知道哪个气瓶在哪个时间连接到哪个气体管道。

我想要技术人员填写两种表格:

  1. 每日库存:每天早上,库房人员需要查看每条燃气管线并记录管路的压力和瓶子的参考编号。这会产生一堆4元组记录(时间,行,瓶,psi);每天早上一行。

  2. 按需更换瓶子:执行每日库存后,如果瓶子几乎已经用完,则需要更改,并且需要记录该更改。这应该为新瓶子的瓶子表添加另一个条目,并为新连接添加另一个带有新(时间,线,瓶,psi)信息的4元组。这种情况每周发生几次,但不是每天都有。

  3. 因此,为了跟踪这一点,我正在编写一个Django应用程序。我有以下型号:

    # models.py
    class GasFarm(models.Model):
        """
        Represents a gas farm -- a collection of lines that are grouped together and managed as a unit.
        """
        name = models.CharField(max_length=30, unique=True)
    
        def __unicode__(self):
            return self.name
    
    class Bottle(models.Model):
        """
        Represents a gas bottle -- the physical cylinder -- that contains a mixture of gases.
        """
    
        # Options
        get_latest_by = 'date_added'
    
        # Fields
        BACKGROUND_TYPES = (
            ('h2/n2', "H2/N2"),
            ('h2/air', "H2/Air"),
            ('h2', "H2"),
            ('n2', "N2"),
            ('other', "Other"),
        )
    
    
        ppm = models.FloatField()
        mix = models.CharField(max_length=50, choices=BACKGROUND_TYPES, default='n2')
        ref = models.CharField(max_length=50, unique=True)  # Every bottle has a unique ref or somebody fucked up.
        cert_date = models.DateTimeField()
        date_added = models.DateTimeField(default=timezone.now())
    
        def pct(self):
            return float(self.ppm)/10**4
    
    
        def __unicode__(self):
            return "{} ({}% {})".format(self.ref, self.pct(), self.mix,)
    
    
    class Line(models.Model):
        """
        Represents a gas line -- the physical plumbing -- that delivers gas from the bottles to the test stations.
    
        It is assumed that a gas line can have zero or one gas bottles attached to it at any given time. The Line model
        maps bottle objects and time-sensitive Reading objects to test stations.
        """
    
        # Fields
        gasfarm = models.ForeignKey(GasFarm)
        number = models.CharField(max_length=10, unique=True)
        bottles = models.ManyToManyField(Bottle, through='Reading')
    
        # Calculated fields. "current" is definitely not optional -- that's a super common query. The others? I'm not so
        # sure...
        def current(self):
            """
            Returns the most recently recorded Reading object associated with the line
            """
            return self.reading_set.latest(field_name='time')
        current.short_description = "latest reading"
    
    
        def last_checked(self):
            """
            Returns the date & time at which the most recent Reading object associated with this line was logged
            """
            return self.current().time
        last_checked.short_description = "last updated"
    
        def has_recent_reading(self):
            """
            Boolean flag for whether the reading is probably valid, or if someone needs to go out and take a new one.
            """
            latest_reading = self.current().time
            return timezone.now() - latest_reading < datetime.timedelta(days=3)
        has_recent_reading.boolean = True
        has_recent_reading.short_description = "Is data current?"
    
    
        def __unicode__(self):
            return self.number
    
    
    class Reading(models.Model):
        """
        A Reading links a Bottle to a Line at a given time, and provides a snapshot of the pressure at that time.
        """
    
        # Options
        get_latest_by = 'time'
    
        # Fields
        line = models.ForeignKey(Line)
        bottle = models.ForeignKey(Bottle)
        time = models.DateTimeField()
        psi = models.IntegerField(validators=[MaxValueValidator(2500)])
    
        def ref(self):
            """
            The reference number of the bottle listed in the reading
            """
            return self.bottle.ref
        def ppm(self):
            """
            The PPM concentration of the bottle listed in the reading
            """
            return self.bottle.ppm
    
        def pct(self):
            """
            The % concentration of the bottle listed in the reading
            """
            return self.bottle.pct()
    
        def mix(self):
            """
            The gas mix (e.g. H2/N2) of the associated bottle
            """
            return self.bottle.mix
    
        def __unicode__(self):
            # Example:
            # A0: 327.3 PPM H2/N2 2300 psi
            return "{}, {}: {} PPM {} {} psi".format(self.line, self.time, self.ppm(), self.mix(), self.psi)
    

    我使用一些脚本使用我们的数据后备日志填充数据库,并且我已经编写了一些视图来提取数据库的 out ;到目前为止,我对他们很满意,结果看起来很有希望 - 至少对于显示存储的数据。

    但我不确定如何使用HTML表单干净地填充数据库。我希望这些表格基本上是两个单独的“工作表” - 就像DMV给你的那种,有很好的清晰指示#justkidding。

    表格1:每日清单

    表单将列出给定服务器场中的所有行,显示每行上应该包含的瓶子(基于先前的读数/更新),然后提示用户输入值。这将要求技术人员在每次提交表格时更新每行的每个瓶子的压力 - 我们想要整个气体系统的全局快照。在一个完美的世界中,表格会预先填充当前时间和每行最近的压力读数到阅读时间和压力字段,以便于数据输入。

    # Cells with brackets [] are system-supplied, non-editable data displayed in the table. 
    # Cells without brackets are pre-filled with sensible defaults, but are user editable.
    |  [Line]  | [Current Bottle]  |  Reading Time   |   Pressure (psi)   |
    ===============================================================
    |   [A0]   |   [15-1478334]    |  2014-7-14 9:34 |       2400         |
    |   [A1]   |   [15-1458661]    |  2014-7-14 9:34 |        500         |
    |   [A2]   |   [15-4851148]    |  2014-7-14 9:34 |       1850         |
    |   [A3]   |   [15-1365195]    |  2014-7-14 9:34 |        700         |
    ...
    ...
    |   [C18]  |   [15-9555813]    |  2014-7-14 9:34 |        2350        |
    |=====================================================================|
    

    在阅读了关于Forms,ModelForms和Formsets的Django文档后,我编写了一些代码,它们几乎我想要的一切 - 但Line和Bottle信息是可编辑的表单字段,并且我需要它们作为静态指南来填写表格的其余部分。但是, do 需要出现在生成的数据库记录中。

    我模糊地意识到readonly和disabled属性,以及当你想在表单中拥有只读内容时,从响应中的POST变量清除数据的kludgy解决方案,但我仍然没有明确这些工作如何或为什么有必要。我想知道是否有一种更清晰的方式来获取我所追求的内容?也许是以编程生成的标题或注释形式?这就是我真正想要的:一个自动生成的指南来填写表单。

    # Forms.py
     class PressureReadingUpdate(forms.ModelForm):
        class Meta:
            model = models.Reading
    
    PsiReadingFormset = formset_factory(PressureReadingUpdate, extra=0)
    
    # views.py
    def update_pressure(request):
        if request.method == 'POST':
            formset = forms.PsiReadingFormset(request.POST)
            if formset.is_valid():
                cd = formset.cleaned_data
                # do something? I'm not here yet...
        else:
            lines = models.Line.objects.all()
            now = datetime.datetime.now()
            initial = [{'line': l,
                        'psi': l.current().psi,
                        "bottle": l.current().bottle,
                        'time': now} for l in lines]
    
            formset = forms.PsiReadingFormset(initial=initial,)
        return render(request, 'gas_tracking/gasfarm_form_psi_reading.html', {'formset': formset})
    

    表格2:更换气瓶

    我想要一份所有气体管线的清单,包括现有的气瓶和气瓶。压力(简单 - 这是在其他地方完成的),然后是一个按钮,它创建一个弹出窗口,您可以在其中提交新瓶子,就像您在管理界面中找到的那样。如何制作弹出窗口?我该如何制作按钮?我甚至不知道从哪个开始呢

    我还是Django的新手,我搜索过高低,但是没有找到任何能回答我问题的东西 - 也许我只是没有使用正确的关键词?

    感谢您的帮助。

    -Matt

1 个答案:

答案 0 :(得分:0)

使用FormSets动态生成表单

所以我想出了这个(经过大量的谷歌搜索和咒骂和咬牙切齿)。马尔科姆·特雷德尼克(Malcolm Tredinnick)发表了一篇关于我想做什么的博客文章,这是一个保存在Django Snippets上的善良灵魂

使用Malcom的代码作为模型,我解决了我的问题,它就像一个魅力

class PressureReadingUpdate(forms.Form):
    """
    Form that asks for the pressure of a line given some attributes of that line.
    """
    psi = forms.IntegerField(widget=forms.NumberInput)

    def __init__(self, *args, **kwargs):
        self.line = kwargs.pop('line')
        kwargs['auto_id'] = "%s".format(self.line.number)
        super(PressureReadingUpdate, self).__init__(*args, **kwargs)

class BasePsiReadingFormset(BaseFormSet):
    """
    Formset that constructs a group of PressureReadingUpdate forms by taking a queryset
    of Line objects and passing each one in turn to a PressureReadingUpdate form as it
    gets constructed
    """
    def __init__(self, *args, **kwargs):
        self.lines = kwargs.pop('lines')
        super(BasePsiReadingFormset, self).__init__(*args, **kwargs)
        self.extra = len(self.lines)
        self.max_num = len(self.lines)

    def _construct_form(self, i, **kwargs):
        kwargs['line'] = self.lines[i]
        form = super(BasePsiReadingFormset, self)._construct_form(i, **kwargs)
        return form

PsiReadingFormset = formset_factory(form=PressureReadingUpdate, formset=BasePsiReadingFormset)

这为你提供了一个带有额外kwarg的Formset,你可以传递给构造函数链。您可以在视图中使用它(以及更典型的initial= kwarg):

formset = PsiReadingFormset(lines=lines,
                            initial=[{'psi': l.current().psi} for l in lines],

所以这是我能想到的最佳解释:

任何传递给FormSet的kwargs(由formset_factory函数使用非默认的'BaseFormSet'作为蓝图)都会传递给__init__ { - 1}} BaseFormSet所基于的FormSet方法。

这意味着您可以在BaseFormSet.__init__中定义自定义行为,并且可以通过将运行时数据作为关键字参数传递给BaseFormSet.__init__(即FormSet来将运行时数据中继到lines=方法我的例子中的kwarg)。我使用它在formset(基于'BasePsiReadingFormset'的FormSet的实例)上设置属性。

困惑了吗?我起初太吵了。

但真正的魔力是了解_construct_forms的工作原理:FormSet每次想要在集合中创建新的Form时都会调用此函数。它会将任何无法识别的kwargs转发给它应该管理一组的Form的构造函数。

因此,您只需要重载自定义BaseFormSet的{​​{1}}以包装超类的原始._construct_forms并注入新的kwarg。然后将此kwarg传递给自定义Form类的构造函数,并根据_construct_forms的初始化函数对新Form实例进行整形。

女士们,先生们,就是如何让FormSet中有一堆类似定义但动态生成且略有不同的形式。

了解了这一点后,我可以看到它的优雅。但是,它使用了一些中级到高级的python,并且既不是立即明显的也不是很好的文档。如果你像我一样苦苦挣扎,请随时给我留言。