/
models.py
176 lines (137 loc) · 5.86 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from core.models import BaseModel
from grid.utils import make_template_fragment_key as grid_make_template_fragment_key
from package.models import Package
class Grid(BaseModel):
"""Grid object, inherits form :class:`package.models.BaseModel`. Attributes:
* :attr:`~grid.models.Grid.title` - grid title
* :attr:`~grid.models.Grid.slug` - grid slug for SEO
* :attr:`~grid.models.Grid.description` - description of the grid
with line breaks and urlized links
* :attr:`~grid.models.Grid.is_locked` - boolean field accessible
to moderators
* :attr:`~grid.models.Grid.packages` - many-to-many relation
with :class:~`grid.models.GridPackage` objects
"""
title = models.CharField(_("Title"), max_length=100)
slug = models.SlugField(
_("Slug"), help_text="Slugs will be lowercased", unique=True
)
description = models.TextField(
_("Description"),
blank=True,
help_text="Lines are broken and urls are urlized",
max_length=1000,
)
is_locked = models.BooleanField(
_("Is Locked"), default=False, help_text="Moderators can lock grid access"
)
packages = models.ManyToManyField(Package, through="GridPackage")
header = models.BooleanField(
_("Header tab?"),
default=False,
help_text="If checked then displayed on homepage header",
)
def elements(self):
elements = []
for feature in self.feature_set.all():
for element in feature.element_set.all():
elements.append(element)
return elements
def __str__(self):
return self.title
@property
def grid_packages(self):
"""Gets all the packages and orders them for views and other things"""
gp = self.gridpackage_set.select_related()
grid_packages = gp.annotate(
usage_count=models.Count("package__usage")
).order_by("-usage_count", "package")
return grid_packages
def save(self, *args, **kwargs):
if self.pk:
self.grid_packages # fire the cache
self.clear_detail_template_cache() # Delete the template fragment cache
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("grid", args=[self.slug])
def get_opengraph_image_url(self):
return reverse("grid_opengraph", args=[self.slug])
def get_detail_template_cache_key(self):
return grid_make_template_fragment_key("detail_template_cache", [str(self.pk)])
def clear_detail_template_cache(self):
key = self.get_detail_template_cache_key()
cache.delete(key)
# delete grid template cache
template_key = make_template_fragment_key(
"html_grid_detail_outer", [str(self.pk)]
)
cache.delete(template_key)
class Meta:
ordering = ["title"]
class GridPackage(BaseModel):
"""Grid package.
This model describes packages listed on one side of the grids
and
explicitly defines the many-to-many relationship between grids
and the packages
(i.e - allows any given package to be assigned to several grids at once).
Attributes:
* :attr:`grid` - the :class:`~grid.models.Grid` to which the package is assigned
* :attr:`package` - the :class:`~grid.models.Package`
"""
grid = models.ForeignKey(Grid, on_delete=models.CASCADE)
package = models.ForeignKey(Package, on_delete=models.CASCADE)
class Meta:
verbose_name = "Grid Package"
verbose_name_plural = "Grid Packages"
def save(self, *args, **kwargs):
self.grid.grid_packages # fire the cache
self.grid.clear_detail_template_cache()
super().save(*args, **kwargs)
def __str__(self):
return f"{self.grid.slug} : {self.package.slug}"
class Feature(BaseModel):
"""These are the features measured against a grid.
``Feature`` has the following attributes:
* :attr:`grid` - the grid to which the feature is assigned
* :attr:`title` - name of the feature (100 chars is max)
* :attr:`description` - plain-text description
"""
grid = models.ForeignKey(Grid, on_delete=models.CASCADE)
title = models.CharField(_("Title"), max_length=100)
description = models.TextField(_("Description"), blank=True, max_length=1000)
def save(self, *args, **kwargs):
self.grid.grid_packages # fire the cache
self.grid.clear_detail_template_cache()
super().save(*args, **kwargs)
def __str__(self):
return f"{self.grid.slug} : {self.title}"
help_text = """
Linebreaks are turned into 'br' tags<br />
Urls are turned into links<br />
You can use just 'check', 'yes', 'good' to place a checkmark icon.<br />
You can use 'bad', 'negative', 'evil', 'sucks', 'no' to place a negative icon.<br />
Plus just '+' or '-' signs can be used but cap at 3 multiples to protect layout<br/>
"""
class Element(BaseModel):
"""The individual cells on the grid.
The ``Element`` grid attributes are:
* :attr:`grid_package` - foreign key to :class:`~grid.models.GridPackage`
* :attr:`feature` - foreign key to :class:`~grid.models.Feature`
* :attr:`text` - the actual contents of the grid cell
"""
grid_package = models.ForeignKey(GridPackage, on_delete=models.CASCADE)
feature = models.ForeignKey(Feature, models.CASCADE)
text = models.TextField(_("text"), blank=True, help_text=help_text, max_length=1000)
class Meta:
ordering = ["-id"]
def save(self, *args, **kwargs):
self.feature.save() # fire grid_packages cache
super().save(*args, **kwargs)
def __str__(self):
return f"{self.grid_package.grid.slug} : {self.grid_package.package.slug} : {self.feature.title}"