I have a model in Django with a method that I can't seem to access in the view. Here is the model:
class Realtor(models.Model):
user = models.OneToOneField(User)
team = models.ForeignKey(RealtorTeam, blank=True, null=True, default=None)
is_showing_realtor = models.BooleanField(default=False)
is_listing_realtor = models.BooleanField(default=False)
def type(self):
if self.is_showing_realtor and self.is_listing_realtor:
return 'Showing and Listing'
elif self.is_showing_realtor:
return 'Showing'
elif self.is_listing_realtor:
return 'Listing'
else:
return ''
Now, in the view, I try to query the model, including the method, like this:
q = Realtor.objects.all()
q = q.filter(user__first_name__icontains=first)
q = q.filter(user__last_name__icontains=last)
q = q.filter(team__name__icontains=team)
q = q.filter(user__email__icontains=email)
q = q.filter(type__icontains=type)
The error I get is Cannot resolve keyword 'type' into field. Choices are.... I looked up a number of stackoverflow questions, and read through more of the Django docs, and I saw to add a field like this in the model:
type_display = (type,)
But that didn't work. Do I have to query the is_showing_realtor and is_listing_realtor? Is there no way to query the method?
More info
We are using Django_tables2 to display tables of models. We are able to access the type method with that, like this:
class RealtorTable(tables.Table):
id = tables.Column()
first_name = tables.Column(accessor='user.first_name', order_by='user.first_name')
last_name = tables.Column(accessor='user.last_name', order_by='user.last_name')
team = tables.Column()
email = tables.Column(accessor='user.email', order_by='user.email')
type = tables.Column(order_by='is_showing_realtor')
But still, can't pass it to the view.
type is the name of a built-in function python, and so also in django. I'd recommend renaming your function to something like listing_type to avoid any clashes.
In particular, this line
type_display = (type,)
probably isn't doing what you think it is.
reptilicus is correct, I'd not spotted the filter on a non-field attribute, that doesn't make sense. You could turn listing_type into a CharField with choices= set, and then some custom save() logic to set it when the model is changed - or you could use property
#def listing_type():
doc = "The listing_type property."
def fget(self):
if self.is_showing_realtor and self.is_listing_realtor:
return 'Showing and Listing'
elif self.is_showing_realtor:
return 'Showing'
elif self.is_listing_realtor:
return 'Listing'
else:
return ''
def fset(self, value):
self._listing_type = value
def fdel(self):
del self._listing_type
return locals()
listing_type = property(**listing_type())
PS I still don't like type :-)
I'm no Django expert, but I'm pretty sure that you can't query on model attributes that aren't columns in the database, which is what you are trying to do. Perhaps try to decorate the method with #property to make it like an attribute.
You could just do a list comprehension if like
results = [m for m in query_set if m.type = "Showing and listing"]
Related
I'm trying to filter my Todos by the test_id pulled from the URL. It pulls the id from the URL but it cant seem to filter with todo__test. I have also tried "test", "Todo.test.test_id", "Todo.test". I guess I'm confused about what variable I need to filter and the Django restframework documentation doesn't explicitly show what variable to use. Their example uses "purchaser__username" which I don't understand where it comes from. https://www.django-rest-framework.org/api-guide/filtering/
class TodoList(generics.ListAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
def get_queryset(self):
test_id = self.kwargs['test_id']
return Todo.objects.filter(todo__test == test_id)
class Todo(models.Model):
test = models.ForeignKey(Test, on_delete=models.CASCADE)
content = models.TextField(blank=True)
order = models.IntegerField()
def __str__(self):
return self.content + ' - ' + self.test.test_name
class Meta:
ordering = ['test_id']
i guess it will be like this. you passed incorrect foreign key field name.
Todo.objects.filter(test_id='whatever_value')
I want to do a query on the django User table like this:
u = User.objects.filter(member__in = member_list)
where:
class Member(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dob = models.DateField('Date of Birth', blank=True, null=True)
and member_list is a list of eligible members.
The query works fine but the problem is I do not actually know the model member is called member. It could be called anything.
I store the name of the model I want in a model called Category. I have a link to the name of the model through content_type.Category is defined as:
class Category(models.Model):
name = models.CharField('Category', max_length=30)
content_type = models.ForeignKey(ContentType)
filter_condition = JSONField(default="{}", help_text=_(u"Django ORM compatible lookup kwargs which are used to get the list of objects."))
user_link = models.CharField(_(u"Link to User table"), max_length=64, help_text=_(u"Name of the model field which links to the User table. 'No-link' means this is the User table."), default="No-link")
def clean (self):
if self.user_link == "No-link":
if self.content_type.app_label == "auth" and self.content_type.model == "user":
pass
else:
raise ValidationError(
_("Must specify the field that links to the user table.")
)
else:
if not hasattr(apps.get_model(self.content_type.app_label, self.content_type.model), self.user_link):
raise ValidationError(
_("Must specify the field that links to the user table.")
)
def __unicode__(self):
return self.name
def _get_user_filter (self):
return str(self.content_type.app_label)+'.'+str(self.content_type.model)+'.'+str(self.user_link)+'__in'
def _get_filter(self):
# simplejson likes to put unicode objects as dictionary keys
# but keyword arguments must be str type
fc = {}
for k,v in self.filter_condition.iteritems():
fc.update({str(k): v})
return fc
def object_list(self):
return self.content_type.model_class()._default_manager.filter(**self._get_filter())
def object_count(self):
return self.object_list().count()
class Meta:
verbose_name = _("Category")
verbose_name_plural = _("Categories")
ordering = ('name',)
So I can retrieve the name of the model that links to User but I then need to convert it into a class which I can include in a query.
I can create an object x = category.content_type.model_class() which gives me <class 'cltc.models.Member'> but when I them perform a query s = User.objects.filter(x = c.category.object_list()) I get the error Cannot resolve keyword 'x' into field.
Any thoughts most welcome.
The left hand side of the filter argument is a keyword, not a python object, so x is treated as 'x', and Django expects a field called x.
To get around this, you can ensure that x is a string, and then use the python **kwarg syntax:
s = User.objects.filter(**{x: c.category.object_list()})
Thanks to https://stackoverflow.com/a/4720109/823020 for this.
I have the following:
# model
TITLE_CHOICES = (
('mr', 'Mr.'),
('ms', 'Ms.'),
('mrs', 'Mrs.'),
('mis', 'Miss.'),
)
class Client(models.Model):
name_title = models.CharField(max_length=3, choices=TITLE_CHOICES)
first_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
# form
class ClientForm(ModelForm):
class Meta:
class = Client
# view
def client_view(request):
client = Client.object.get(id=1)
clientForm = ClientForm(instance=client)
return render_to_response('client.html',{'client':client,
'clientForm':clientForm}, ...)
# client.html
...
How can I loop through the object client printing out the column name and the value while making sure that if the value is a choice it prints out the human-readable choice value, not the stored value (get_title_display)?
And why is this not eaiser to do in Django? (isn't this a common thing to want do?)
If I can't do this I have to go statically through each column and use get_title_display, which means that there is no separation between model and template, which means if I change my model I have to manually update the template(s). This is not good
Try something like:
# add to your Client model
def get_fields(self):
fields_display = []
for f in Client._meta.fields:
name = f.name
if len(f.choices) == 0:
fields_display.append([name, f.value_to_string(self)])
else:
fields_display.append([name, getattr(self,"get_%s_display" % name)()])
return fields_display
You can then loop over get_fields in your template for a given object
If you want to get get_FOO_display by default, you have to overwrite the __getattribute__ method. Try something like this:
class FooModel(models.Model):
...
def __getattribute__(self, item):
get = lambda i: object.__getattribute__(self, i)
name_map = get('_meta')._name_map
if item.startswith('_') or name_map.has_key(item):
return get(item)
else:
field = name_map.get(item)
if field.choices:
return get('get_%s_display' % item)()
else:
return get(item)
So right now I'm editting the Querydict that the modelform gives to the view to make the submission in one field all lowercase and have no spaces. but then when I construct and pass that dictionary back to the Modelform to be validated/saved it doesn't give me an error if the same thing has been entered more than once. It seems like unique=True should work for all submissions that are in the correct format not just ones from request.POST. Any help/insight on the issue would be awesome.
EDIT: CODE
THE VIEW THAT HANDLES THE MODELFORM
dict = {}
sitename = request.POST['sitename']
#insert an if statement telling them only letters are allowed
urltitle = ''.join(sitename.split()).lower()
dict['sitename'] = urltitle
make = MakesiteForm(dict)
if make.is_valid():
make.save()
MODEL IN QUESTION
class Makesite(models.Model):
sitename = models.CharField(max_length=100, unique = True)
siteinfo = models.ManyToManyField(Siteinfo)
ref_id = models.ManyToManyField(RefID)
def __unicode__(self):
return u'%s' %(self.sitename)
1.Don't reassign built-in dict function
2.Field processing logic should be done in clean method:
class MakesiteForm(forms.ModelForm):
# your code... Then
def clean_sitename(self):
sitename = self.cleaned_data['sitename']
return ''.join(sitename.split()).lower()
3.Show what errors you get if form is not valid?
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
django update modelform
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 ForeignKey to User. A book will only have one User at any given time, but presumably Users 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's clean method. Something like:
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.')
Now, you checkout method has a number of issues. First, status should be a defined set of choices, not just random strings.
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)
Then, you can run your checks like:
if self.status == self.STATUS_AVAILABLE:
self.status = self.STATUS_CHECKED_OUT
You could use strings and a CharField instead 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, history needs to be a ManyToManyField. 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.
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
Which then allows you to get full histories:
some_item.history.all()
some_user.item_history.all()
To add a new history, you would do:
ItemHistory.objects.create(item=some_item, user=some_user, activity=ItemHistory.CHECKED_OUT)
The auto_now_add attribute ensures that the timestamp is automatically set when the relationship is created.
You could then actually get rid of the last_checkout and last_activity fields entirely and use something like the following:
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)
And, you can then use them as normal:
some_item.last_checkout
Finally, your checkout method is not an override of save so it's not appropriate to call super(ItemObject, self).save(). Just use self.save() instead.