joeni

models.py

1
from django.db import models
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