我正在建立一个图书馆系统来管理我们办公室的某些项目,我不需要一个完整的集成图书馆系统,所以我决定用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_has
是EmailField
,因为我无法获得CharfField
使用登录用户的名称填充,但我认为使用OneToOneField
可能更接近“正确”的方式来执行此操作..虽然who_has
是EmailField
,但以下方法工作:
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
我是否正确地使用了一种改进的save()方法,或者是否可以使用ModelForm子类?
编辑/更新:非常感谢@ChrisPratt!
所以我试图让Chris Pratt建议显示ItemHistory,但是当我尝试在页面上呈现它时,我得到一个AttributeError
,指出“'User'对象没有属性'timestamp'” 。所以我的问题是,当User
是last_activity
对象的属性时,为什么抱怨ItemObject
对象?
我的观点:
@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领域,其目标是用户。那就是原因!
答案 0 :(得分:3)
假设用户将自己登录并查看图书,那么您最想要的是ForeignKey
到User
。一本书在任何给定时间只有一个User
,但大概User
也可以查看其他项目。如果存在某些限制,即使每个用户的限制实际上是一个,最好在模型的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.')
现在,checkout方法有很多问题。首先,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_checkout
和last_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()
代替。