I need to access an attribute from a related model (to use as a filter attribute in an admin), but I get this error: 'EventTimeAdmin.list_filter[1]' refers to field 'event__sites' that is missing from model 'EventTime'.
Here is are the relevant classes from models.py:
class Event(models.Model):
title = models.CharField(max_length=100)
short_description = models.CharField(max_length=250, blank=True)
long_description = models.TextField(blank=True)
place = models.ForeignKey(Place, related_name="events", default=0, blank=True, null=True)
one_off_place = models.CharField('one-off place', max_length=100, blank=True)
phone = models.CharField(max_length=40)
cost_low = models.DecimalField('cost (low)', max_digits=7, decimal_places=2, blank=True, null=True)
cost_high = models.DecimalField('cost (high)', max_digits=7, decimal_places=2, blank=True, null=True)
age_lowest = models.PositiveSmallIntegerField('lowest age', blank=True, null=True, help_text='Use 0 for "all ages". Leave blank if no age information is available') # Not every event submits info about age limits
priority = models.ForeignKey(EventPriority)
ticket_website = models.URLField('ticket Web site', blank=True)
posted_date = models.DateTimeField('date entered', default=datetime.datetime.now())
updated_date = models.DateTimeField('date updated', editable=False, blank=True)
small_photo = models.ImageField(upload_to='img/events/%Y', blank=True)
teaser_text = models.TextField(max_length=1000, blank=True)
is_ongoing_indefinitely = models.BooleanField(db_index=True, help_text="If this box is checked, the event won't be displayed by default in the event search. Users will have to manually select \"Display ongoing events\" in order to display it.")
is_national_act = models.BooleanField('is a national marquee touring act')
categories = models.ManyToManyField(EventCategory, related_name="events")
bands = models.ManyToManyField(Band, related_name="events", blank=True)
sites = models.ManyToManyField(Site)
related_links = generic.GenericRelation(RelatedLink)
generic_galleries = generic.GenericRelation(GalleryRelationsPiece)
staff_photographer_attending = models.BooleanField('Staff Photographer will be attending')
index = EventIndex()
class Meta:
ordering = ('title',)
def __unicode__(self):
return self.title
def save(self, *args, **kwargs):
self.updated_date = datetime.datetime.now()
super(Event, self).save(*args, **kwargs)
def get_absolute_url(self):
if self.is_recurring():
return reverse('ellington_events_ongoing_detail', args=[self.id])
else:
next = self.get_next_time()
if next:
return next.get_absolute_url()
else:
try:
last = self.event_times.order_by("-event_date", "start_time")[0]
return last.get_absolute_url()
except IndexError:
return reverse('ellington_events_todays_events', args=[])
@property
def _sites(self):
"""A property used for indexing which sites this object belongs to."""
return [s.id for s in self.sites.all()]
# def start_time(self):
# return self.event_times.start_time
# TODO: Redundant
def get_absolute_url_recurring(self):
"Returns the absolute URL for this event's recurring-event page"
return reverse('ellington_events_ongoing_detail', args=[self.id])
def is_recurring(self):
"""Returns ``True`` if this event is a recurring event."""
return bool(self.recurring_event_times.count())
def get_cost(self):
"Returns this event's cost as a pretty formatted string such as '$3 - $5'"
if self.cost_low == self.cost_high == 0:
return 'Free'
if self.cost_low == self.cost_high == None:
return ''
if self.cost_low == 0 and self.cost_high == None:
return 'Free'
elif self.cost_low == None and self.cost_high == 0:
return 'Free'
if self.cost_low == self.cost_high:
cost = '$%.2f' % self.cost_low
elif self.cost_low == 0 or self.cost_low == None:
cost = 'Free - $%.2f' % self.cost_high
elif self.cost_high == 0 or self.cost_high == None:
cost = '$%.2f' % self.cost_low
else:
cost = '$%.2f - $%.2f' % (self.cost_low, self.cost_high)
return cost.replace('.00', '')
def get_age_limit(self):
"Returns this event's age limit as a pretty formatted string such as '21+'"
if self.age_lowest is None:
return 'Not available'
if self.age_lowest == 0:
return 'All ages'
return '%s+' % self.age_lowest
def get_place_name(self):
"Returns this event's venue, taking into account the one_off_place field."
if self.one_off_place:
return self.one_off_place
if self.place is not None:
return self.place.name
else:
return ""
def get_next_time(self):
"""
Return the next (in the future) ``EventTime`` for this ``Event``.
Assumes the event is not recurring. Returns ``None`` if no next time is
available.
"""
now = datetime.datetime.now()
for et in self.event_times.filter(event_date__gte=now.date()).order_by("event_date", "start_time"):
try:
dt = datetime.datetime.combine(et.event_date, et.start_time)
except TypeError:
return None
if dt >= now:
return et
return None
# TODO: proprietary
def get_lj_category(self):
"""
Returns either 'Nightlife', 'Music', 'Theater', 'Misc.' or
'Museums and Galleries' for this event.
"""
MAPPING = (
('Activities', 'Misc.'),
('Art', 'Museums and Galleries'),
('Children', 'Misc.'),
('Community', 'Misc.'),
('Culinary', 'Misc.'),
('KU calendar', 'Misc.'),
('Lectures', 'Misc.'),
('Museums', 'Museums and Galleries'),
('Performance', 'Misc.'),
)
# "Performance | Theater" is a special case.
for c in self.categories.all():
if str(c) == "Performance | Theater":
return 'Theater'
parent_categories = [c.parent_category for c in self.categories.all()]
for parent_category, lj_category in MAPPING:
if parent_category in parent_categories:
return lj_category
# Failing that, we must be in either 'Music', which can be either
# 'Music' or 'Nightlife', depending on the venue.
if self.one_off_place:
return 'Music'
# If it's downtown, it's 'Nightlife'. Otherwise, it's 'Music'.
if "downtown" in self.place.neighborhood.lower():
return 'Nightlife'
return 'Music'
class EventTime(models.Model):
event = models.ForeignKey(Event, related_name="event_times")
event_date = models.DateField('date')
start_time = models.TimeField(blank=True, null=True)
finish_time = models.TimeField(blank=True, null=True)
objects = EventTimeManager()
def long_desc(self):
return self.event.long_description
# def sites(self):
# return self.event._sites
# def sites(self):
# return self.event.sites
# @property
# def _sites(self):
# """A property used for indexing which sites this object belongs to."""
# return [s.id for s in self.event.sites.all()]
# def _sites(self):
# """A property used for indexing which sites this object belongs to."""
# return [s.id for s in self.event.sites.all()]
# def _sites(self):
# """A property used for indexing which sites this object belongs to."""
# return [s.id for s in event.sites.all()]
class Meta:
ordering = ('event_date', 'start_time')
def __unicode__(self):
return u"%s -- %s at %s" % (self.event.title, self.event_date.strftime("%m/%d/%y"), self.event.get_place_name())
def get_absolute_url(self):
year = self.event_date.strftime("%Y")
month = self.event_date.strftime("%b").lower()
day = self.event_date.strftime("%d")
return reverse('ellington_events_event_detail', args=[year, month, day, self.event.id])
def get_time(self):
"Returns this event's time as a pretty formatted string such as '3 p.m. to 5 p.m.'"
if self.start_time is not None and self.finish_time is not None:
return '%s to %s' % (dateformat.time_format(self.start_time, 'P'), dateformat.time_format(self.finish_time, 'P'))
elif self.start_time is not None:
return dateformat.time_format(self.start_time, 'P')
return 'time TBA'
def get_part_of_day(self):
"""
Returns a string describing the part of day this event time is taking
place -- either 'Morning', 'Afternoon', 'Evening' or 'Night'.
"""
if self.start_time is None:
return 'Time not available'
if 3 < self.start_time.hour < 12:
return 'Morning'
elif 12 <= self.start_time.hour < 18:
return 'Afternoon'
elif 18 <= self.start_time.hour < 21:
return 'Evening'
return 'Night'
def is_in_the_past(self):
return self.event_date < datetime.date.today()
def happens_this_year(self):
return datetime.date.today().year == self.event_date.year
def get_other_event_times(self):
"Returns a list of all other EventTimes for this EventTime's Event"
if not hasattr(self, "_other_event_times_cache"):
self._other_event_times_cache = [et for et in self.event.event_times.order_by("event_date", "start_time") if et.id != self.id]
return self._other_event_times_cache
# TODO: proprietary
def get_weather_forecast(self):
"Returns a weather.forecast.DayForecast object for this EventTime"
try:
from ellington.weather.models import DayForecast
except ImportError:
return None
if not self.event.place.is_outdoors:
raise DayForecast.DoesNotExist
if not hasattr(self, "_weather_forecast_cache"):
self._weather_forecast_cache = DayForecast.objects.get(full_date=self.event_date)
return self._weather_forecast_cache
DAY_OF_WEEK_CHOICES = (
(0, 'Sunday'),
(1, 'Monday'),
(2, 'Tuesday'),
(3, 'Wednesday'),
(4, 'Thursday'),
(5, 'Friday'),
(6, 'Saturday'),
)
The thing is, event__title
is accessed from EventTimeAdmin
:
class EventTimeAdmin(admin.ModelAdmin):
list_display = ('event', 'event_date', 'start_time', 'finish_time','long_desc')
list_filter = ('event_date','event__sites')
search_fields = ('event__title', 'event_date')
ordering = ('-event_date',)
class Meta:
model = EventTime
But event__sites
is not accessible... why is that?
Edit: You can see my attempts (commented out) to define sites
in models.py above. None of these worked.