django autoincrement based on foreignkey - python

Let's say I have a model Issue that looks like this:
class Issue(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
text = models.TextField()
And another one called Comment like so:
class Comment(models.Model):
issue = models.ForeignKey(Issue)
text = models.TextField()
number = models.AutoField()
I want the comment number field to be relative to the issue.
Which means that every issue will have its separate comment#1, comment#2, comment#3...etc.
Can this be done in Django?

If you removed the number field from your Comment model and replaced it with a date_added field, you could still query the DB to pull all comments associated with a certain entry in order.
class Comment(models.Model):
issue = models.ForeignKey(Issue)
text = models.TextField()
date_added = models.DateField(auto_now_add = True)
Then if you wanted to get all of the comments associated with a certain issue:
i = Issue.object.get(pk=1) #pk = primary key id for whatever issue you want
comments_by_issue = i.comment_set.all().order_by('date_added')
Now you have some comments that you can refer to by index location (comments_by_issue[0] would get you the first comment attached to the issue, for instance).
That index location is the closest way I can figure to get what you're looking for. Someone else mentioned comment.id, but this is just going to be an autoincrementing integer that goes up for every comment. The fifth comment added to your system might have comment.id = 5, but it might be the first comment attached to issue 2 - if I'm reading your query right, having the comment ID doesn't help in that context.
This is more of a workaround than a direct answer, but I hope it helps.

I prefer soundeux's solution. That said, here is the idea...
class Comment(models.Model):
# ...
number = models.PositiveIntegerField(default=1)
def save(self, *args, **kwargs):
obj = super(Comment, self).save(*args, **kwargs)
try:
last_comment = self.issue.comment_set.all().orderby('-number')[0]
except KeyError: # I think it's KeyError... for when there is no comments
pass
else:
Comment.objects.filter(id=self.id).update(number=last_comment.number+1)
return obj
Be careful - this will not be executed when Comments are being updated using the ORM's update method. Indeed, that's what I've used to avoid recursion (which is not a very good solution either). You might wanna implement it as a pre_save event instead of the save() (still won't respond to the update(), but will be more robust about potential recursion problems).

Related

Django: How to order comments of a post directly under each original post object?

I’m creating a django app that allows posting something on each user’s page and then allowing people to comment on each of those posts. I’m trying to get the associated comments for each post and display them right under each post in timestamp order. I have figured out how to do this for the original posts of each profile using allpageposts = username.newpost_set.all().order_by('-postdate'), but can’t seem to figure out what the best way to do it for comments even though it seems at first it’d be the same type of logic as the original posts. The problem I am having is that I need to keep track of which comments are for which specific ‘newpost’ in a user’s page which can have many ‘newposts’ on their page. What is a good way that I can capture each page's newpost's comments and display them in a template? Thanks for any tips or hints. Here are my models:
class newpost(models.Model):
newlinktag = models.ForeignKey('username')
postcontent = models.CharField(max_length=1024)
postdate = models.DateTimeField()
postlikes = models.IntegerField(null=False, default=0)
def __unicode__(self):
return self.postcontent
class postcomment(models.Model):
comment = models.CharField(max_length=1024, null=False)
commenttag = models.ForeignKey('newpost')
postcommentdate = models.DateTimeField()
commentlikes = models.IntegerField(null=False, default=0)
def __unicode__(self):
return self.comment
If I've understand you correct, you need something like this:
for post in username.newpost_set.all():
comments = postcomment.objects.filter(commenttag=post).order_by('-postcommentdate')
# your code here

Include Queryset Key in String Format

I am trying to run a system command with the value of a field from a queryset but not having much luck. And it is returning 'Jobs' object is not subscriptable'. Please see below for relevant information.
Models.py
class Jobs(models.Model):
user_id = models.CharField(max_length=100)
template = models.CharField(max_length=100)
change_ref = models.CharField(max_length=100)
summary = models.CharField(max_length=100)
category = models.CharField(max_length=100)
Views
def delete_job(request, job_id):
record = Jobs.objects.get(pk=int(job_id))
os.system('mkdir /home/username/' + record['summary'])
return HttpResponseRedirect("/configen")
I am passing the job_id in through the URL which seems to work fine ( I can delete the record no problem). I was under the impression the 'get' would simply get one record, which I could then reference as a dictionary?
I'm sure there is a simple solution, it doesn't seem to work with string formatting either (using %s or .format()) methods.
Thank you in advance
You're correct that get does get one record, but wrong that you can reference it as a dictionary. It's a model instance, so you use the normal dot notation: record.summary.

Django relationship backward works without _set but not with

I have the following database tables:
class Story(models.Model):
user = models.ForeignKey(User)
group = models.ForeignKey(Group, blank=True, null=True)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
location = models.CharField(max_length=100)
title = models.CharField(max_length=150)
description = models.CharField(blank=True, null=True, max_length=2000)
exp_text = models.TextField()
category = models.ForeignKey(Category, blank=True, null=True)
def __unicode__(self):
return self.title
class Comment(models.Model):
user = models.ForeignKey(User)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
emailOnReply = models.NullBooleanField(blank=True, null=True)
comment_text = models.TextField()
story = models.ForeignKey(Story)
def __unicode__(self):
return self.comment_text
When i have created an object of Comment and need to access the title of a story i would expect to be able to do this:
c = Comment.objects.all()
for com in c:
com.comment_set.title
but django says that comment_set isnt defined. In the documentation it says that if you are tring to reach a field in the other database table where the foreign is not defined you can use the _set method where the word before it is the name of the class.
After trying different ways i found that this works:
c = Comment.objects.all()
for com in c:
com.story.title
Since the foreign key is defined in Comment i dont see how that can work, but it does, and why doesnt the _set work. Since im working on an object in the model where the foreign key is defined and i need to go to the story then i would need to use _set according to the docs...when working with a story objects im able to refer straigt to the comment model when defining the related_name property so i dont need _set there either.. why doesnt _set work here?
Update:
I now got the relationship working backwards when using a story object and refering to the comment class by:
s = Story.objects.all()
for st in s:
print st.comment_set.all()
I was using st.story_set instead of st.comment_set but i still find it weird that this works:
c = Comment.objects.all()
for com in c:
print com.story.title
When trying to work from the story object i dont have any foreign key to the comment table (only a related name on the foreign key in the comment table) so i dont get the same access it seems.
Actually everything is working as expected.
Comment object doesn't have a comment foreign key, it has a "story" foreign key.
comments are "pointing" to stories. as such - a comment only has one story, but a story may have a "set" of comments.
This is why st.comment_set works - because it "looks backward" to the comments pointing to it, while a comment just points directly to the story it is related to (i.e. com.story).
if you are interested in understanding why that works - see here:
https://docs.djangoproject.com/en/dev/topics/db/queries/#how-are-the-backward-relationships-possible
that's part of what makes Django's ORM so cool...
Following relationships in Django is a bit implicit. Here are simplified versions of your models:
class Story(models.Model): # instances have access to Manager comment_set (backward)
title = models.CharField(max_length=150)
description = models.CharField(blank=True, null=True, max_length=2000)
class Comment(models.Model): # has access to Story (forward)
comment_text = models.TextField()
story = models.ForeignKey(Story) # stored in database as story_id, which refers to (implicit) id column in Story
Your database will look like this (unless you specify otherwise):
Story (table)
id # automatically inserted by Django, unless you specify otherwise
title
description
Comment (table)
id # automatically inserted by Django, unless you specify otherwise
comment_text
story_id # points back to id in Story table
FORWARD
Instances of Comment have access to Story by matching Comment's story_id column to Story's id column (the latter being implicit to Django tables and not in your model but definitely in your database table, unless you have specified otherwise).
>>> c = Comment.objects.get(comment_text='hello, world')
>>> c.story.title # c => story_id => row in Story where id == c.story_id
'first post'
BACKWARD
Comment has a ForeignKey pointing back to Story, so instances of Story have access to a Manager named comment_set that can follow relationships from Story to Comment.
>>> s = Story.objects.get(title='first post')
>>> s.comment_set.comment_text # accesses Comment model from Story instance
'hello, world'
Or if you want to iterate over all comment_sets as you mention in your comment, try this instead:
>>> s = Story.objects.get(title='first post') # returns a single, non-iterable query object
>>> for story in s.comment_set.all(): # each comment_set object can have more than one item
print story.comment_text # story does not have a comment_set attribute
'hello, world' # my comment set here just happens to have one item
UPDATE
Or, based upon your comment, you want to take the iteration to one level higher, try the following:
>>> s = Story.objects.all()
>>> for story in s: # iterate over top level
>>> for row in story.comment_set.all():
>>> print row.comment_text # again, row does not have a comment_set attribute
'hello, world' # again, my comment set has only one item

Fetch two objects beside eachother in Django

I'm creating Django application, with forum-like thingy. One of the views should display list of Discussions, with last written post beside it.
class Discussion(models.Model):
<snip>
topic = models.CharField(max_length=512)
class DiscussionPost(models.Model):
<snip>
target = models.ForeignKey(Discussion)
author = models.ForeignKey(User)
content = models.TextField(max_length=16000)
creation_date = models.DateTimeField(auto_now_add=True)
With standard Django queries, I would have to fetch ~50 times per page (one for each discussion).
DiscussionPost.objects
.filter(target=some_discussion)
.annotate(last_post=Max('creation_date'))
.filter(creation_date=F('last_post'))
I tried to work this around by adding field last_post = models.ForeignKey(DiscussionPost, null=True) to discussion, and changing 'save' method in DiscussionPost like this:
def save(self, *args, **kwargs):
if self.pk == None:
i_am_new = True
else:
i_am_new = False
super(DiscussionPost, self).save(*args, **kwargs)
if i_am_new:
self.target.last_post=self
self.target.save()
But this makes circular dependency, and simply won't compile.
Does anyone know a way to solve this problem? It seems easy, but I'm stuck...
To solve your circular dependency:
The problem is: DiscussionPost has not been declared yet when you FK it in Discussion.
put the name of the model that has not been declared yet in quotes.
models.ForeignKey('DiscussionPost', null=True)
see: https://stackoverflow.com/a/9606701/884453

Integer Field Math in Django

from django.db import models
from django.contrib.auth.models import User
class Product(models.Model):
name = models.CharField(max_length = 127)
description = models.TextField()
code = models.CharField(max_length = 30)
lot_no = models.CharField(max_length = 30)
inventory = models.IntegerField()
commited = models.IntegerField()
available = models.IntegerField()
reorder = models.IntegerField()
created_date = models.DateField(auto_now_add = True)
comment_user = models.ForeignKey(User, null=True)
comment_txt = models.TextField()
def __unicode__(self):
return self.code + " - " + self.name + " - " + self.lot_no + " - " + str(self.created_date)
#property
def available(self):
return self.inventory - self.commited
Im trying to have available calculated by (inventory - self) when a person enters in the data for inventory and commited in django admin template. But I'm not sure how.
Thanks,
Jon
Try overriding the save method on the model:
def save(self, *args, **kwargs):
"update number available on save"
self.available = self.inventory - self.committed
super(Product, self).save(*args, **kwargs)
You could also put logic in there that would do something if self.available became negative.
It seems like you may have two problems; the overlapping available property+field and availability not showing up as you expect in the admin.
Choose one way (property or field) to represent the availability and go with it. Don and Seth have shown a way to do it using a field and Daniel and Ignacio have suggested going with a property.
Since you really want this field to show up in the admin just go with the field; give it a helpful help_text="...", remove the #property, and override save().
class Product(models.Model):
# ...
availability = models.IntegerField(help_text="(updated on save)")
# Use Seth's save()
def save(self, *args, **kwargs):
self.availability = self.inventory - self.commited
super(Product, self).save(*args, **kwargs)
This is not the best way to do things in terms of normalized data but it will probably be the simplest solution to your current problem.
If you are using trunk instead of Django-1.1.1, you can also use readonly_fields in the admin.
Don is correct that you have the name available duplicated, because you have both a field and a property. Drop the field.
This is what I said when I gave you the solution to this problem in your original question - I explicitly said "drop the existing 'available' field". Following half a solution is never going to work.
However I fundamentally disagree with Seth and Don who recommend overriding the save() function to calculate this value. That is a totally unnecessary duplication of data. The property is the correct solution.
You've bound both a Django field and a vanilla Python property to the same name on your model. One of these attributes is masking the other, which is why you're getting unexpected behavior in the Django admin. This is almost certainly not what you want.
Override the save method and remove your def available property entirely.
Ignacio is trying to help you keep your data normalized by not storing information in your DB twice. It's a good practice to follow in the general case, but there are many times when you want to store calculated values in your DB. This seems like a practical use of data duplication.
The property is actually removing the available integer field off the admin page it seems
See the Django documentation for model properties

Categories

Resources