I am working on a library system to manage certain items in our office, I don’t need a full-blown integrated library system so I decided to hand roll one with Django.
Below is a simplified version of my model:
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)
At first I was using a method on ItemObject to process checking out an item to a user and who_has was an EmailField because I couldn’t get a CharfField to populate with the logged in user’s name, but I figured using a OneToOneField is probably closer to the “right” way to do this.. While who_has was an EmailField, the following method worked:
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()
Now that I am using a OneToOneField this doesn’t work, so I started looking at using a subclass of ModelForm but none of the cases I saw here on SO seemed to apply for what I am trying to do; my form would be a button, and that’s it. Here are some of the questions I looked at:
Django: saving multiple modelforms simultaneously (complex case)
(Django) (Foreign Key Issues) model.person_id May not be NULL
So was I on the right track with a sort of altered save() method, or would a ModelForm subclass be the way to go?
EDIT/UPDATE: Many thanks to @ChrisPratt!
So I am trying to get Chris Pratt’s suggestion for showing ItemHistory to work, but when I try to render it on a page I get an AttributeError that states “‘User’ object has no attribute ‘timestamp'”. So my question is, why is it complaining about a User object when last_activity is an attribute on the ItemObject object ?
My view:
@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 })
I do not see why a User object is coming up at this point.
EDIT2: Nevermind, history is clearly a M2M field whose target is User. That’s why!
Assuming users will log in and check out books to themselves, then what you most likely want is a
ForeignKeytoUser. A book will only have oneUserat any given time, but presumablyUsers could check out other items as well. If there is some limit, even if the limit is actually one per user, it would be better to validate this in the model’scleanmethod. Something like:Now, you checkout method has a number of issues. First,
statusshould be a defined set of choices, not just random strings.Then, you can run your checks like:
You could use strings and a
CharFieldinstead if you like, as well. The key is to decouple the static text from your code, which allows much greater flexibility in your app going forward.Next,
historyneeds to be aManyToManyField. Right now, your “history” is only who last checked the item out or what the last item the user checked out was, and as a result is pretty useless.Which then allows you to get full histories:
To add a new history, you would do:
The
auto_now_addattribute ensures that the timestamp is automatically set when the relationship is created.You could then actually get rid of the
last_checkoutandlast_activityfields entirely and use something like the following:And, you can then use them as normal:
Finally, your checkout method is not an override of
saveso it’s not appropriate to callsuper(ItemObject, self).save(). Just useself.save()instead.