models.py
1 |
|
2 |
from django.utils.translation import ugettext_lazy as _ |
3 |
from joeni import constants |
4 |
|
5 |
def validate_hex_color(value): |
6 |
pass # TODO |
7 |
|
8 |
class Account(models.Model): |
9 |
user = models.OneToOneField( |
10 |
'administration.User', |
11 |
on_delete=models.CASCADE, |
12 |
primary_key=True, |
13 |
) |
14 |
alias = models.CharField(max_length=64, unique=True) |
15 |
|
16 |
def __str__(self): |
17 |
return self.alias |
18 |
|
19 |
def account_user_directory(instance, filename): |
20 |
return '{0}/account/settings/{1}'.format(instace.account.alias, filename) |
21 |
|
22 |
class AccountSettings(models.Model): |
23 |
account = models.OneToOneField( |
24 |
'Account', |
25 |
on_delete=models.CASCADE, |
26 |
primary_key=True, |
27 |
) |
28 |
# TODO: Build validator for primary_color to make sure what is given is a |
29 |
# valid hexadecimal RGB value. |
30 |
color = models.CharField( |
31 |
max_length=6, |
32 |
help_text=_("The hexadecimal code of the color for this account."), |
33 |
default = constants.COLORS["UHasselt default"], |
34 |
blank=False, |
35 |
validators=[validate_hex_color], |
36 |
) |
37 |
account_page_banner = models.ImageField( # Requires the Pillow library! |
38 |
upload_to=account_user_directory, |
39 |
help_text=_("The banner image to be shown on this account's homepage."), |
40 |
) |
41 |
avatar = models.ImageField( |
42 |
upload_to=account_user_directory, |
43 |
help_text=_("The avatar image of this account."), |
44 |
) |
45 |
|
46 |
def __str__(self): |
47 |
return str(self.account) |
48 |
|
49 |
class Post(models.Model): |
50 |
timestamp = models.DateTimeField(auto_now_add=True) |
51 |
title = models.CharField( |
52 |
max_length=64, |
53 |
blank=True, |
54 |
help_text=_("The title for this post."), |
55 |
) |
56 |
text = models.TextField( |
57 |
blank=True, |
58 |
help_text=_("A text message for this post. May be left blank."), |
59 |
) |
60 |
author = models.ForeignKey( |
61 |
"Account", |
62 |
on_delete=models.CASCADE, |
63 |
null=False, # There must be an author |
64 |
#editable=False, # It makes no sense to change the author after creation |
65 |
help_text=_("The authoring account of this post."), |
66 |
) |
67 |
response_to = models.ForeignKey( |
68 |
"self", |
69 |
on_delete=models.CASCADE, |
70 |
null=True, # If this is null, this post is not a response, but a beginning post |
71 |
#editable=False, # This cannot be changed after creation, wouldn't make sense |
72 |
related_name="responses", |
73 |
help_text=_("The post to which this was a response, if applicable."), |
74 |
) |
75 |
placed_on = models.ForeignKey( |
76 |
"Page", |
77 |
on_delete=models.CASCADE, |
78 |
null=False, |
79 |
#editable=False, |
80 |
help_text=_("The page on which this post was placed."), |
81 |
) |
82 |
# Voting fields |
83 |
allow_votes = models.BooleanField( |
84 |
default=True, |
85 |
help_text=_("Decide whether to allow voting or disable it for this post."), |
86 |
) |
87 |
allow_responses = models.BooleanField( |
88 |
default=True, |
89 |
help_text=_("Decide if other people can respond to this post or not. " |
90 |
"This does not influence what people allow on their posts."), |
91 |
) |
92 |
|
93 |
def votes(self): |
94 |
""" Return how many votes this post has. """ |
95 |
return len(Vote.objects.filter(post=self)) |
96 |
|
97 |
def __str__(self): |
98 |
return str(self.timestamp) + " | " + str(self.author) |
99 |
# TODO Add a way to attach geographical data to a post, which could |
100 |
# then be used with OpenStreetMap or something |
101 |
|
102 |
class FilePost(Post): |
103 |
""" A special type of Post, which has a file linked with it. |
104 |
The poster can specify how to treat this file. """ |
105 |
image = _("Image") |
106 |
video = _("Video") |
107 |
music = _("Sound") |
108 |
text = _("Text" ) |
109 |
other = _("Other") |
110 |
file = models.FileField( |
111 |
upload_to="agora/posts/%Y/%m/%d/", |
112 |
null=False, |
113 |
#editable=False, |
114 |
help_text=_("The file you wish to share."), |
115 |
) |
116 |
file_type = models.CharField( |
117 |
max_length=16, |
118 |
blank=False, |
119 |
choices = ( |
120 |
('image', image), |
121 |
('video', video), |
122 |
('music', music), |
123 |
('text' , text ), |
124 |
('other', other), |
125 |
), |
126 |
help_text=_("How this file should be seen as."), |
127 |
) |
128 |
|
129 |
class Page(models.Model): |
130 |
""" In the university, people can create pages for everything they want and |
131 |
then some. These pages are put in the database through this table. """ |
132 |
name = models.CharField( |
133 |
max_length=64, |
134 |
primary_key=True, |
135 |
blank=False, |
136 |
help_text=_("The name of this page."), |
137 |
) |
138 |
created = models.DateTimeField(auto_now_add=True) |
139 |
hidden = models.BooleanField( |
140 |
default=False, |
141 |
help_text=_("Determines if this page can be found without a direct link."), |
142 |
) |
143 |
main_content = models.TextField( |
144 |
blank=True, |
145 |
help_text=_("If you want to put some text on this page, " |
146 |
"you can put it here. You can use Orgmode-syntax to " |
147 |
"get as much out of your page as possible. While doing so, " |
148 |
"be aware of the limitations imposed by the code of conduct."), |
149 |
) |
150 |
public_posting = models.BooleanField( |
151 |
default=True, |
152 |
help_text=_("Determines if everyone can post on this page, or only the " |
153 |
"people that are linked with it. Know that if a post is made " |
154 |
"and responding is allowed, everyone can respond to that post."), |
155 |
) |
156 |
|
157 |
"""class Meta: |
158 |
abstract=True""" |
159 |
""" I've disabled the abstraction, because otherwise I can't relate a Post to a Page. """ |
160 |
|
161 |
class AccountPage(Page): |
162 |
""" Every account has its own homepage. This is that page. |
163 |
This page can only be edited by the account holder, or staff members. """ |
164 |
# TODO: Find a way to auto-create one of these every time a new account is created |
165 |
# TODO: Require that changes can only occur by either the account holder or staff |
166 |
account = models.OneToOneField( |
167 |
"Account", |
168 |
null=False, |
169 |
on_delete=models.CASCADE, |
170 |
) |
171 |
|
172 |
class GroupPage(Page): |
173 |
""" A page where a group can present itself to the university. |
174 |
This page can only be edited by group members or staff members. """ |
175 |
# TODO: Find a way to auto-create one of these every time a new group is created |
176 |
# TODO: Require that changes can only occur by either the group or staff |
177 |
group = models.ForeignKey( |
178 |
"Group", |
179 |
null=False, |
180 |
on_delete=models.CASCADE, |
181 |
) |
182 |
|
183 |
class CoursePage(Page): |
184 |
""" A page that serves as a course's main entry point. |
185 |
This page can only be edited by the course's educating team or staff members. """ |
186 |
# TODO: Find a way to auto-create one of these every time a new course is created |
187 |
# TODO: Require that changes can only occur by either the course team or staff |
188 |
course = models.OneToOneField( |
189 |
"courses.Course", |
190 |
null=False, |
191 |
on_delete=models.CASCADE, |
192 |
) |
193 |
|
194 |
class Group(models.Model): |
195 |
""" It is imperative that everyone can come together with other people. |
196 |
A Group record is the way to accomplish this. """ |
197 |
name = models.CharField( |
198 |
max_length=64, |
199 |
primary_key=True, # So be unique I'd say |
200 |
blank=False, |
201 |
help_text=_("The name of your group."), |
202 |
) |
203 |
color = models.CharField( |
204 |
max_length=6, |
205 |
help_text=_("The hexadecimal code of the color for this group."), |
206 |
default = constants.COLORS["UHasselt default"], |
207 |
blank=False, |
208 |
validators=[validate_hex_color], |
209 |
) |
210 |
members = models.ManyToManyField( |
211 |
"Account", |
212 |
help_text=_("The members of this group."), |
213 |
) |
214 |
invite_only = models.BooleanField( |
215 |
default=True, |
216 |
help_text=_("Determines if everyone can join this group, or if " |
217 |
"only members can invite others."), |
218 |
) |
219 |
private = models.BooleanField( |
220 |
default=True, |
221 |
help_text=_("Determines if this group is visible to non-members."), |
222 |
) |
223 |
|
224 |
def __str__(self): |
225 |
return self.name |
226 |
|
227 |
|
228 |
class AccountCollection(models.Model): |
229 |
""" Every account can make a collection in which (s)he can list accounts |
230 |
at his/her wish. This can be a collection of Friends, study collegues, |
231 |
project partners, and so on. |
232 |
Accounts that are in a certain collection are not notified of this. |
233 |
However, there is one exception: |
234 |
If both accounts have a collection named "Friends" (or the localized |
235 |
equivalent), and both feature each other in that collection, then |
236 |
this is shared between the two accounts. """ |
237 |
account = models.ForeignKey( |
238 |
"Account", |
239 |
null=False, |
240 |
#editable=False, |
241 |
on_delete=models.CASCADE, |
242 |
help_text=_("The account that created this collection."), |
243 |
) |
244 |
name = models.CharField( |
245 |
max_length=32, |
246 |
blank=False, |
247 |
help_text=_("The name of this collection."), |
248 |
) |
249 |
accounts = models.ManyToManyField( |
250 |
"Account", |
251 |
help_text=_("All accounts that are part of this collection."), |
252 |
related_name="in_collection", |
253 |
) |
254 |
visible_to_public = models.BooleanField( |
255 |
default=False, |
256 |
help_text=_("Make this collection visible to everybody."), |
257 |
) |
258 |
visible_to_collection = models.BooleanField( |
259 |
default=True, |
260 |
help_text=_("Make this collection visible to the accounts in this collection. Other collections are not affected by this."), |
261 |
) |
262 |
|
263 |
def __str__(self): |
264 |
return str(self.account) + " | " + self.name |
265 |
|
266 |
class Vote(models.Model): |
267 |
""" Accounts can vote on posts (using ▲, which is funny because UHasselt). |
268 |
These votes are registered in this table. """ |
269 |
voter = models.ForeignKey( |
270 |
"Account", |
271 |
null=False, |
272 |
#editable=False, |
273 |
on_delete=models.CASCADE, |
274 |
) |
275 |
post = models.ForeignKey( |
276 |
"Post", |
277 |
null=False, # Duh. |
278 |
#editable=False, # Transferring votes doesn't make sense |
279 |
on_delete=models.CASCADE, |
280 |
) |
281 |
|
282 |
class SharedFile(models.Model): |
283 |
""" Groups and people can share files with each other, through a chat system. |
284 |
These files are represented here. """ |
285 |
timestamp = models.DateTimeField(auto_now_add=True) |
286 |
file = models.FileField( |
287 |
upload_to="agora/chat/%Y/%m/%d/", |
288 |
null=False, |
289 |
#editable=False, |
290 |
help_text=_("The file you want to share."), |
291 |
) |
292 |
uploader = models.ForeignKey( |
293 |
"Account", |
294 |
on_delete=models.CASCADE, |
295 |
null=False, |
296 |
#editable=False, |
297 |
help_text=_("The account that uploaded this file."), |
298 |
) |
299 |
# TODO __str__ ... or maybe not? |
300 |
|
301 |
class Message(models.Model): |
302 |
""" Everyone can communicate with someone else using private messages. |
303 |
These messages are recorded here. """ |
304 |
timestamp = models.DateTimeField(auto_now_add=True) |
305 |
text = models.TextField( |
306 |
blank=False, |
307 |
) |
308 |
sender = models.ForeignKey( |
309 |
"Account", |
310 |
on_delete=models.CASCADE, |
311 |
null=False, |
312 |
#editable=False, |
313 |
help_text=_("The account that sent this message."), |
314 |
) |
315 |
# TODO __str__ |
316 |
|
317 |
|
318 |
class Chat(models.Model): |
319 |
""" Chats can happen between a group, or between two people in private. |
320 |
These messages are connected to a particular chat. """ |
321 |
messages = models.ManyToManyField( |
322 |
"Message", |
323 |
help_text=_("All messages that were shared in this chat."), |
324 |
) |
325 |
shared_files = models.ManyToManyField( |
326 |
"SharedFile", |
327 |
help_text=_("The files that are shared in this chat."), |
328 |
) |
329 |
class Meta: |
330 |
abstract=True |
331 |
|
332 |
class GroupChat(Chat): |
333 |
""" Represents a chat of a group. """ |
334 |
group = models.ForeignKey( |
335 |
"Group", |
336 |
on_delete=models.CASCADE, |
337 |
null=False, |
338 |
#editable=False, |
339 |
) |
340 |
|
341 |
def __str__(self): |
342 |
return str(self.group) |
343 |
|
344 |
class PrivateChat(Chat): |
345 |
""" Represents a private chat between two accounts. """ |
346 |
# FIXME: It's theoretically possible to start a chat by one person, and have |
347 |
# the other person start the same as well. Find a reliable way to block that. |
348 |
account1 = models.ForeignKey( |
349 |
"Account", |
350 |
on_delete=models.CASCADE, |
351 |
null=False, |
352 |
#editable=False, |
353 |
) |
354 |
account2 = models.ForeignKey( |
355 |
"Account", |
356 |
on_delete=models.CASCADE, |
357 |
null=False, |
358 |
#editable=False, |
359 |
related_name="correspondent", |
360 |
) |
361 |
|
362 |
def __str__(self): |
363 |
return str(self.account1) +" - "+ str(self.account2) |
364 |
|
365 |
class GroupInvite(models.Model): |
366 |
""" Groups can send invites to other people to join the group, and this is |
367 |
not limited to private groups. Group invitations are stored here, as well as their outcome. """ |
368 |
inviter = models.ForeignKey( |
369 |
"Account", |
370 |
on_delete=models.CASCADE, |
371 |
null=False, |
372 |
#editable=False, |
373 |
) |
374 |
invitee = models.ForeignKey( |
375 |
"Account", |
376 |
on_delete=models.CASCADE, |
377 |
null=False, |
378 |
#editable=False, |
379 |
help_text=_("The account which will receive the invitation."), |
380 |
related_name="invitee", |
381 |
) |
382 |
group = models.ForeignKey( |
383 |
"Group", |
384 |
on_delete=models.CASCADE, |
385 |
null=False, |
386 |
#editable=False, |
387 |
db_index=True, |
388 |
help_text=_("The group for which this invitation is."), |
389 |
) |
390 |
accepted = models.NullBooleanField( |
391 |
default=None, |
392 |
help_text=_("Indicates if the invitation was accepted, rejected, or " |
393 |
"pending an answer. if somebody rejects the invitation, " |
394 |
"that group can no longer send an invitation to the invitee, " |
395 |
"unless (s)he removes the answer from her history. Also, " |
396 |
"a person can not reject an invitation, and accept it later. " |
397 |
"For that, a new invitation must be received."), |
398 |
) |
399 |
|
400 |
def __str__(self): |
401 |
return str(self.invitee) +" | "+ str(self.group) |
402 |