I'm trying to put together a simple little app that allows users to keep track of their own pokemon. Each pokemon has 1-2 different 'types' (e.g. fire, water, etc.), so I've added in a clean function to limit the maximum number of types the user can select. However, when trying to add a pokemon I am given the following error:
AttributeError at /admin/pokollector/custompokemon/add/
'CustomPokemon' object has no attribute 'poke_types'
I'm assuming this has something to do with the poke_types variable not being properly inherited, but I have no idea why that would be.
Here is the code from my models.py file:
from django.db import models
from django.core.validators import MinValueValidator as min, MaxValueValidator as max
from django.core.exceptions import ValidationError
class PokeType(models.Model):
poke_type = models.CharField(max_length=15)
def __str__(self):
return self.poke_type
#Current generation of games for gen_added field
gen = 8
class Pokemon(models.Model):
poke_name = models.CharField(max_length=30)
poke_type = models.ManyToManyField(PokeType)
evolves_from = False
evolves_into = False
gen_added = models.PositiveIntegerField(validators=[min(1), max(gen)])
def clean(self):
#Allow max of 2 poke_types to be selected
if len(self.poke_types > 2):
raise ValidationError('A Pokemon has a maximum of two types.')
class Meta:
verbose_name_plural = 'Pokemon'
abstract = True
class CustomPokemon(Pokemon):
name = models.CharField(max_length=30)
level = models.PositiveIntegerField(blank=True, null=True)
def __str__(self):
return self.name
I think there is problem in your clean function. Try.
def clean(self):
#Allow max of 2 poke_types to be selected
if self.poke_type.count() > 2:
raise ValidationError('A Pokemon has a maximum of two types.')
It seems you mistyped.
Plus, don't use len function. When you use len, the count happens on python which is slow. Use count function so that the count occurs in database level.
Just had a few typos with the attribute name and having the comparison inside the len() call.
def clean(self):
#Allow max of 2 poke_types to be selected
if len(self.poke_type) > 2:
raise ValidationError('A Pokemon has a maximum of two types.')
Related
I am working on a django app that creates random fantasy character names that pull from the following models:
class VillagerFirstNames(models.Model):
first_name=models.CharField(max_length=30, unique=True)
race = models.ForeignKey(Race, on_delete=models.CASCADE)
def __str__(self):
return self.first_name
class VillagerLastNames(models.Model):
last_name = models.CharField(max_length=30, unique=True)
race = models.ForeignKey(Race, on_delete=models.CASCADE)
def __str__(self):
return self.last_name
My issue is arising in my Views. In order to pull a random.sample I have to convert my query to a list like so:
foreign_first = list(VillagerFirstNames.objects.all()
foreign_first_random = random.sample(foreign_first, 3)
context["foreign_first"] = foreign_first_random
foreign_last = list(VillagerLastNames.objects.filter(race__race=foreign_first_random.race))
context["foreign_last"] = random.sample(foreign_last, 3)
Basically, I want the last names pulled to be of the same race as the ones pulled in the first random sample. I'm having trouble figuring this one out, since the way I'm doing it above takes away the "race" attribute from foreign_first_random.
You can do random selection in Race, from there you can select random VillagerFirstNames and VillagerLastNames. For example:
race = Race.objects.all().order_by('?').first()
race_firstname = race.villagerfirstname_set.all().order_by('?').first()
race_lastname = race.villagerlastname_set.all().order_by('?').first()
Here order_by('?') makes the queryset random.
Update
To pass the values to template, you can try like this:
context["foreign_first"] = race.villagerfirstname_set.order_by('?')[:5]
context["foreign_last"] = race.villagerlastname_set.order_by('?')[:5]
I'm trying to unit test a model but I keep getting "Donation matching query does not exist," with the traceback pointing to the first line in the test_charity function. I tried getting the object with charity='aclu' instead of by ID, but that did not fix it.
from django.test import TestCase
from .models import Donation
class DonateModelTest(TestCase):
def init_data(self):
#print("got here")
x = Donation.objects.create(charity='aclu', money_given=15)
# print(x.id)
def test_charity(self):
donation = Donation.objects.get(id=1)
field_label = donation._meta.get_field('charity').verbose_name
self.assertEquals(field_label, 'charity')
My models.py:
from django.db import models
class Donation(models.Model):
DONATE_CHOICES = [
('aclu', 'American Civil Liberties Union'),
('blm', 'Black Lives Matter'),
('msf', 'Medecins Sans Frontieres (Doctors Without Borders)')
]
charity = models.CharField(
max_length=4,
choices=DONATE_CHOICES,
default='aclu'
)
money_given = models.IntegerField(default=0)
You setUp data with setUp. Furthermore you should save the primary key, and use this since a database can use any primary key. Depending on the database backend, and the order of the test cases, it thus can create an object with a different primary key:
class DonateModelTest(TestCase):
def setUp(self):
self.pk = Donation.objects.create(charity='aclu', money_given=15).pk
def test_charity(self):
donation = Donation.objects.get(id=self.pk)
field_label = donation._meta.get_field('charity').verbose_name
self.assertEquals(field_label, 'charity')
I've an Order model and others models which related with it. An user can delete any of this items and I must perform a check if the order is empty after deletion and set as active False in case true. Some basic code to ilustrate it
class Order(models.Model):
paid = models.BooleanField(default=False)
active = models.BooleanField(default=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
def empty_order():
"""
I must implement it
"""
class HomeOrder(models.Model):
...
order = models.OneToOneField(Order, related_name='primary_home')
class TourOrder(models.Model):
...
order = models.ForeignKey(Order, related_name='tours')
I have a post_delete signals that are connected with every of this Models related to Order:
post_delete.connect(delete_order_if_empty, sender=HomeOrder)
post_delete.connect(delete_order_if_empty, sender=TourOrder)
def delete_order_if_empty(sender, instance, **kwargs):
if instance.order.empty_order():
instance.order.active = False
instance.order.save()
An Order can have one Home, so if the Home exists I can do order.primary_home, if Home does not exist it will raise an AttributeError because it is an OneToOne relationship.
An Order can have many Tours, so in the empty_order method I thought to do some checks as following.
def empty_order():
home = hasattr(self, 'primary_home') # Avoid AttributeError exception
tours = self.tours.exists()
this_order_has_something = primary_home or tours
return not this_order_has_something
Now, when I delete an HomeOrder the signal is raised but the empty_method never realized that this HomeOrder does not exists any more. Example:
>>>o=Order.objects.create(...)
>>>o.primary_home # raise AttributeError
>>>h=HomeOrder.objetcs.create(order=o, ...)
>>>o.primary_home # <HomeOrder: home-xx>
>>>h.delete()
>>>o.primary_home # still <HomeOrder: home-xx> Why?
>>>o.refresh_from_db()
>>>o.primary_home # again <HomeOrder: home-xx>
I have some Django models that look something like this (this isn't my exact code but is a simpler example that has the same structure):
class Player(models.Model):
# Some fields here.
pass
class Team(models.Model):
players = models.ManyToManyField(Player, through='TeamPlayer')
class TeamPlayer(models.Model):
team = models.ForeignKey(Team)
player = models.ForeignKey(Player)
some_other_field = models.BooleanField()
I'm using the through mechanism because I have extra columns on my link table.
My admin classes look something like this (note that I am using an inline admin to add the players):
class TeamPlayerInline(admin.TabularInline):
model = TeamPlayer
max_num = 11
extra = 11
class TeamAdmin(admin.ModelAdmin):
inlines = [TeamPlayerInline]
admin.site.register(Team, TeamAdmin)
The Question: My problem is that in my admin I would like to validate that a team has exactly 11 players. Any fewer should result in an error. How can I do this?
These are the things that I have tried and the reasons why they didn't work:
Validate the number of players in the clean method of the Team model. This doesn't work because the players haven't been saved yet, so for a new object there are always zero players.
Validate the number in the clean_players method of a ModelForm used by the TeamAdmin. This method never gets called. Similar methods for other non-ManyToMany fields do get called.
Validate the number in the clean method of the aforementioned ModelForm. This method gets called but the self.cleaned_data dictionary does not have an entry for 'players'.
Any ideas how I can achieve this type of validation? I'm far from being a Django expert so don't assume that I've necessarily done everything that should be obvious.
You need to set the formset on the TeamPlayerInline. And override the clean method in that form set. For example:
from django.forms.models import BaseInlineFormSet
class TeamPlayerFormset(BaseInlineFormSet):
def clean(self):
"""Check that exactly 11 players are entered."""
super(TeamPlayerFormset, self).clean()
if any(self.errors):
return
count = 0
for cleaned_data in self.cleaned_data:
if cleaned_data and not cleaned_data.get('DELETE', False):
count += 1
if count != 11:
raise forms.ValidationError('You must enter 11 team players.')
class TeamPlayerInline(admin.TabularInline):
model = TeamPlayer
max_num = 11
extra = 11
formset = TeamPlayerFormset
class TeamAdmin(admin.ModelAdmin):
inlines = [TeamPlayerInline]
[Update: Changed question title to be more specific]
Sorry if I didn't make the question very well, I can't figure how to do this:
class WhatEver():
number = model.IntegerField('Just a Field', default=callablefunction)
...
Where callablefunction does this query:
from myproject.app.models import WhatEver
def callablefunction():
no = WhatEver.objects.count()
return no + 1
I want to automatically write the next number, and I don't know how to do it.
I have errors from callablefunction stating that it cannot import the model, and I think there must be an easier way to do this. There's no need even to use this, but I can't figure how to do it with the pk number.
I've googled about this and the only thing I found was to use the save() method for auto incrementing the number... but I wanted to show it in the <textfield> before saving...
What would you do?
Got it! I hope this will help everyone that has any problems making a auto-filled and auto-incrementing field in django. The solution is:
class Cliente(models.Model):
"""This is the client data model, it holds all client information. This
docstring has to be improved."""
def number():
no = Cliente.objects.count()
if no == None:
return 1
else:
return no + 1
clientcode = models.IntegerField(_('Code'), max_length=6, unique=True, \
default=number)
[... here goes the rest of your model ...]
Take in care:
The number function doesn't take any arguments (not even self)
It's written BEFORE everything in the model
This was tested on django 1.2.1
This function will automatically fill the clientcode field with the next number (i.e. If you have 132 clients, when you add the next one the field will be filled with clientcode number 133)
I know that this is absurd for most of the practical situations, since the PK number is also auto-incrementing, but there's no way to autofill or take a practical use for it inside the django admin.
[update: as I stated in my comment, there's a way to use the primary key for this, but it will not fill the field before saving]
Every Django model already has an auto-generated primary key:
id = models.AutoField(primary_key=True)
It seems you are trying to duplicate an already existing behavior, just use the object primary key.
I, too, came across this problem, my instance of it was customer.number which was relative to the customers Store. I was tempted to use something like:
# Don't do this:
class Customer(models.Model):
# store = ...
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
try:
self.number = self.store.customer_set.count() + 1
else:
self.number = 1
super(Customer, self).save(*args, **kwargs)
The above can cause several problems: Say there were 10 Customers, and I deleted customer number 6. The next customer to be added would be (seemingly) the 10th customer, which would then become a second Customer #10. (This could cause big errors in get() querysets)
What I ended up with was something like:
class Store(models.Model):
customer_number = models.IntegerField(default=1)
class Customer(models.Model):
store = models.ForeignKey(Store)
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
self.number = self.store.customer_number
self.store.number += 1
self.store.save()
super(Customer, self).save(*args, **kwargs)
PS:
You threw out several times that you wanted this field filled in "before". I imagine you wanted it filled in before saving so that you can access it. To that I would say: this method allows you to access store.customer_number to see the next number to come.
You have errors in code, that's why you can't import it:
from django.db import models
class WhatEver(models.Model):
number = models.IntegerField('Just a Field', default=0)
and Yuval A is right about auto-incrementing: you don't even need to declare such a field. Just use the pk or id, they mean the same unless there's a composite pk in the model:
> w = Whatever(number=10)
> w
<Whatever object>
> w.id
None
> w.save()
> w.id
1
[update] Well, I haven't tried a callable as a default. I think if you fix these errors, it must work.