【问题标题】:Django-powered library checkout systemDjango 驱动的图书馆借阅系统
【发布时间】:2011-11-18 15:56:40
【问题描述】:

我正在开发一个图书馆系统来管理我们办公室的某些项目,我不需要一个成熟的集成图书馆系统,所以我决定用 Django 手动推出一个。

以下是我的模型的简化版本:

class ItemObjects(models.Model):

# Static Variables
IN_STATUS        = 'Available'
OUT_STATUS       = 'Checked out'
MISSING          = 'Missing'
STATUS_CHOICES   = (
    (IN_STATUS,  'Available'),
    (OUT_STATUS, 'Checked out'),
    (MISSING,    'Missing'),
)

# Fields
slug             = models.SlugField(unique=True)
date_added       = models.DateField(auto_now_add=True)
last_checkin     = models.DateTimeField(editable=False, null=True)
last_checkout    = models.DateTimeField(editable=False, null=True)
last_activity    = models.DateTimeField(editable=False, null=True)
status           = models.CharField(choices=STATUS_CHOICES, default=IN_STATUS, max_length=25)
who_has          = models.OneToOneField(User, blank=True, null=True)
times_out        = models.PositiveIntegerField(default=0, editable=False)
notes            = models.CharField(blank=True, max_length=500)
history          = models.TextField(blank=True, editable=False)
pending_checkin  = models.BooleanField(default=False)
pending_transfer = models.BooleanField(default=False)

起初我使用ItemObject 上的一种方法来处理向用户签出项目,而who_hasEmailField,因为我无法获得CharfField 来填充登录用户的名称,但我认为使用 OneToOneField 可能更接近“正确”的方式来执行此操作。虽然 who_hasEmailField,但以下方法有效:

    def check_out_itemobject(self, user):
        user_profile                     = user.get_profile()
        if self.status == 'Available' and self.who_has == '':
            self.status                  = 'Checked out'
            self.who_has                 = user.email
            self.last_checkout           = datetime.datetime.now()
            self.last_activity           = datetime.datetime.now()
            self.times_out               += 1
            if self.history == '':
                self.history             += "%s" % user_profile.full_name
            else:
                self.history             += ", %s" % user_profile.full_name
            if user_profile.history == '':
                user_profile.history     += self.title
            else:
                user_profile.history     += ", %s" % self.title
        else:
            return False # Not sure is this is "right"
        user_profile.save()
        super(ItemObjects, self).save()

现在我使用OneToOneField 这不起作用,所以我开始考虑使用ModelForm 的子类,但我在这里看到的所有案例似乎都不适用于我正在尝试做的事情;我的表单将是一个按钮,就是这样。以下是我查看的一些问题:

Django: saving multiple modelforms simultaneously (complex case)

(Django) (Foreign Key Issues) model.person_id May not be NULL

django update modelform

那么我是在正确的轨道上使用一种改变的 save() 方法,还是 ModelForm 子类是要走的路?

编辑/更新:非常感谢@ChrisPratt!

所以我试图让 Chris Pratt 的建议显示 ItemHistory 工作,但是当我尝试在页面上呈现它时,我得到一个 AttributeError ,指出“'用户'对象没有属性'时间戳'”。所以我的问题是,当last_activityItemObject 对象的属性时,为什么它会抱怨User 对象?

我的看法:

@login_required
def item_detail(request, slug):
    item      = get_object_or_404(Item, slug=slug)
    i_history = item.last_activity
    user      = request.user

    return render_to_response('items/item_detail.html',
                              { 'item'     : item,
                                'i_history': i_history,
                                'user'     : user })

我不明白为什么此时会出现 User 对象。

EDIT2:没关系,历史显然是一个 M2M 字段,其目标是用户。这就是为什么!

【问题讨论】:

    标签: python django forms checkout


    【解决方案1】:

    假设用户将自己登录并签出书籍,那么您最可能需要的是ForeignKeyUser。一本书在任何给定时间都只有一个User,但大概Users 也可以查看其他项目。如果有一些限制,即使限制实际上是每个用户一个,最好在模型的clean 方法中验证这一点。比如:

    def clean(self):
        if self.who_has and self.who_has.itemobject_set.count() >= LIMIT:
            raise ValidationError('You have already checked out your maximum amount of items.')
    

    现在,您的结帐方法存在许多问题。首先,status 应该是一组已定义的选项,而不仅仅是随机字符串。

    class ItemObject(models.Model):
        AVAILABLE = 1
        CHECKED_OUT = 2
        STATUS_CHOICES = (
            (AVAILABLE, 'Available'),
            (CHECKED_OUT, 'Checked Out'),
        )
    
        ...
    
        status = models.PositiveIntegerField(choices=STATUS_CHOICES, default=AVAILABLE)
    

    然后,您可以像这样运行检查:

    if self.status == self.STATUS_AVAILABLE:
        self.status = self.STATUS_CHECKED_OUT
    

    如果您愿意,也可以使用字符串和CharField。关键是将静态文本与您的代码分离,这可以让您的应用在未来具有更大的灵活性。

    接下来,history 必须是 ManyToManyField。现在,您的“历史记录”只是上次签出该项目的人或用户签出的最后一个项目是什么,因此毫无用处。

    class ItemObject(models.Model):
        ...
        history = models.ManyToManyField(User, through='ItemHistory', related_name='item_history', blank=True)
    
    class ItemHistory(models.Model):
        CHECKED_OUT = 1
        RETURNED = 2
        ACTIVITY_CHOICES = (
            (CHECKED_OUT, 'Checked Out'),
            (RETURNED, 'Returned'),
        )
    
        item = models.ForeignKey(ItemObject)
        user = models.ForeignKey(User)
        activity = models.PostiveIntegerField(choices=ACTIVITY_CHOICES)
        timestamp = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            ordering = ['-timestamp'] # latest first
    

    然后可以让您获得完整的历史记录:

    some_item.history.all()
    some_user.item_history.all()
    

    要添加新的历史记录,您可以:

    ItemHistory.objects.create(item=some_item, user=some_user, activity=ItemHistory.CHECKED_OUT)
    

    auto_now_add 属性确保在创建关系时自动设置时间戳。

    然后您实际上可以完全摆脱 last_checkoutlast_activity 字段并使用类似以下内容:

    class ItemObject(models.Model):
        ...
        def _last_checkout(self):
            try:
                return self.history.filter(activity=ItemHistory.CHECKED_OUT)[0].timestamp
            except IndexError:
                return None
        last_checkout = property(_last_checkout)
    
        def _last_activity(self):
            try:
                return self.history.all()[0].timestamp
            except IndexError:
                return None
        last_activity = property(_last_activity)
    

    然后,您可以照常使用它们:

        some_item.last_checkout
    

    最后,您的结帐方法不是save 的覆盖,因此调用super(ItemObject, self).save() 是不合适的。只需改用self.save()

    【讨论】:

    • 只是几个 cmets:1) 我推荐 related_name='history'ItemObject.history,因为这是 User 的历史。 2) 你能做some_item.history.create(user=some_user, activity=ItemHistory.CHECKED_OUT) 而不是通过ItemHistory.objects 吗? 3) 我建议对last_checkout 使用@property 装饰器。
    • 感谢克里斯·普拉特!我想我应该澄清一下,但我们没有施加结帐限制。我喜欢你对历史所做的一切,现在就试一试!!编辑:还要感谢 Mike DeSimone!
    • 我选择了item_history 而不是history 作为相关名称,因为可以预见User 可能与它相关联的其他“历史”。 ItemObject 将永远只有一个“历史”,所以缩短的形式在那里很好。
    • @Chris,我没有提到这一点,因为我认为它不相关,但 ItemObject 模型实际上是 Abstract 模型,然后我将其子类化为特定的项目类型。我是否正确地认为我需要为每个 ItemObject 子类创建一个单独的 ForeignKey,或者一个 ForeignKeyItemObject 就足够了?
    • 哦,是的,那会是个问题。您需要在直通表上使用通用外键,但我认为这是不可能的。即使是这样,维持它也可能是一场噩梦。您可以尝试为直通表上的每个子类创建一个外键,每个子类都使用blank=True, null=True,但这似乎也很笨拙。您唯一的其他选择是放弃使类抽象并使用 MTI(多表继承),或者根据您的子类所做的事情,您可能能够制作这些代理模型并且只有一个表用于 ItemObject .
    猜你喜欢
    • 2015-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    • 2015-06-03
    • 1970-01-01
    相关资源
    最近更新 更多