1 | from django.db import models |
---|
2 | |
---|
3 | import datetime |
---|
4 | import os, errno |
---|
5 | |
---|
6 | import settings |
---|
7 | import groups.models |
---|
8 | from util.misc import log_and_ignore_failures, mkdir_p |
---|
9 | import util.previews |
---|
10 | |
---|
11 | class FYSM(models.Model): |
---|
12 | group = models.ForeignKey(groups.models.Group) |
---|
13 | display_name = models.CharField(max_length=50) |
---|
14 | year = models.IntegerField() |
---|
15 | website = models.URLField() |
---|
16 | join_url = models.URLField(verbose_name="recruiting URL", help_text="""<p>If you have a specific web page for recruiting new members of your group, you can link to it here. It will be used as the destination for most links about your group (join link on the main listing page and when clicking on the slide, but not the "website" link on the slide page). If you do not have such a page, use your main website's URL.</p>""") |
---|
17 | contact_email = models.EmailField(help_text="Give an address for students interested in joining the group to email (e.g., an officers list)") |
---|
18 | description = models.TextField(help_text="Explain in about three or four sentences what your group does and why incoming freshmen should get involved. If you go over 400 characters, we may ask you to cut your description, cut it ourselves, and/or display only the beginning on the group listing pages.") |
---|
19 | logo = models.ImageField(upload_to='fysm/logos', blank=True, ) |
---|
20 | slide = models.ImageField(upload_to='fysm/slides', default="", help_text="Upload a slide to display on the group detail page. This will be scaled to be at most 600x600 pixels. We recommend making it exactly that size.") |
---|
21 | tags = models.CharField(max_length=100, blank=True, help_text="Specify some free-form, comma-delimited tags for your group", ) |
---|
22 | categories = models.ManyToManyField('FYSMCategory', blank=True, help_text="Put your group into whichever of our categories seem applicable.", ) |
---|
23 | join_preview = models.ForeignKey('PagePreview', null=True, ) |
---|
24 | |
---|
25 | def save(self, *args, **kwargs): |
---|
26 | if self.join_preview is None or self.join_url != self.join_preview.url: |
---|
27 | self.join_preview = PagePreview.allocate_page_preview( |
---|
28 | filename='fysm/%d/group%d'%(self.year, self.group.pk, ), |
---|
29 | url=self.join_url, |
---|
30 | ) |
---|
31 | super(FYSM, self).save(*args, **kwargs) # Call the "real" save() method. |
---|
32 | |
---|
33 | def __str__(self, ): |
---|
34 | return "%s (%d)" % (self.display_name, self.year, ) |
---|
35 | |
---|
36 | class Meta: |
---|
37 | verbose_name = "FYSM submission" |
---|
38 | |
---|
39 | class FYSMCategory(models.Model): |
---|
40 | name = models.CharField(max_length=25) |
---|
41 | slug = models.SlugField() |
---|
42 | blurb = models.TextField() |
---|
43 | |
---|
44 | def __str__(self, ): |
---|
45 | return self.name |
---|
46 | |
---|
47 | class Meta: |
---|
48 | verbose_name = "FYSM category" |
---|
49 | verbose_name_plural = "FYSM categories" |
---|
50 | ordering = ['name', ] |
---|
51 | |
---|
52 | class FYSMView(models.Model): |
---|
53 | when = models.DateTimeField(default=datetime.datetime.now) |
---|
54 | fysm = models.ForeignKey(FYSM, null=True, blank=True, ) |
---|
55 | year = models.IntegerField(null=True, blank=True, ) |
---|
56 | page = models.CharField(max_length=20, blank=True, ) |
---|
57 | referer = models.URLField(verify_exists=False, null=True, ) |
---|
58 | user_agent = models.CharField(max_length=255) |
---|
59 | source_ip = models.IPAddressField() |
---|
60 | source_user = models.CharField(max_length=30, blank=True, ) |
---|
61 | |
---|
62 | @staticmethod |
---|
63 | @log_and_ignore_failures(logfile=settings.LOGFILE) |
---|
64 | def record_metric(request, fysm=None, year=None, page=None, ): |
---|
65 | record = FYSMView() |
---|
66 | record.fysm = fysm |
---|
67 | record.year = year |
---|
68 | record.page = page |
---|
69 | if 'HTTP_REFERER' in request.META: |
---|
70 | record.referer = request.META['HTTP_REFERER'] |
---|
71 | record.user_agent = request.META['HTTP_USER_AGENT'] |
---|
72 | record.source_ip = request.META['REMOTE_ADDR'] |
---|
73 | record.source_user = request.user.username |
---|
74 | record.save() |
---|
75 | |
---|
76 | class PagePreview(models.Model): |
---|
77 | update_time = models.DateTimeField(default=datetime.datetime.utcfromtimestamp(0)) |
---|
78 | url = models.URLField() |
---|
79 | image = models.ImageField(upload_to='page-previews', blank=True, ) |
---|
80 | |
---|
81 | never_updated = datetime.datetime.utcfromtimestamp(0) # Never updated |
---|
82 | update_interval = datetime.timedelta(hours=23) |
---|
83 | |
---|
84 | def image_filename(self, ): |
---|
85 | return os.path.join(settings.MEDIA_ROOT, self.image.name) |
---|
86 | |
---|
87 | |
---|
88 | @classmethod |
---|
89 | def allocate_page_preview(cls, filename, url, ): |
---|
90 | preview = PagePreview() |
---|
91 | preview.update_time = cls.never_updated |
---|
92 | preview.url = url |
---|
93 | preview.image = 'page-previews/%s.jpg' % (filename, ) |
---|
94 | image_filename = preview.image_filename() |
---|
95 | mkdir_p(os.path.dirname(image_filename)) |
---|
96 | try: |
---|
97 | os.symlink('no-preview.jpg', image_filename) |
---|
98 | except OSError as exc: |
---|
99 | if exc.errno == errno.EEXIST: |
---|
100 | pass |
---|
101 | else: raise |
---|
102 | preview.save() |
---|
103 | return preview |
---|
104 | |
---|
105 | def update_preview(self, ): |
---|
106 | self.update_time = datetime.datetime.now() |
---|
107 | self.save() |
---|
108 | failure = util.previews.generate_webpage_preview(self.url, self.image_filename(), ) |
---|
109 | if failure: |
---|
110 | self.update_time = self.never_updated |
---|
111 | self.save() |
---|
112 | |
---|
113 | @classmethod |
---|
114 | def previews_needing_updates(cls, interval=None, ): |
---|
115 | if interval is None: |
---|
116 | interval = cls.update_interval |
---|
117 | before = datetime.datetime.now() - interval |
---|
118 | return cls.objects.filter(update_time__lte=before) |
---|
119 | |
---|
120 | @classmethod |
---|
121 | def update_outdated_previews(cls, interval=None, ): |
---|
122 | previews = cls.previews_needing_updates(interval) |
---|
123 | now = datetime.datetime.now() |
---|
124 | update_list = [] |
---|
125 | previews_dict = {} |
---|
126 | for preview in previews: |
---|
127 | update_list.append((preview.url, preview.image_filename(), )) |
---|
128 | previews_dict[preview.url] = preview |
---|
129 | preview.update_time = now |
---|
130 | preview.save() |
---|
131 | failures = util.previews.generate_webpage_previews(update_list) |
---|
132 | for url, msg in failures: |
---|
133 | print "%s: %s" % (url, msg, ) |
---|
134 | preview = previews_dict[url] |
---|
135 | preview.update_time = cls.never_updated |
---|
136 | preview.save() |
---|
137 | |
---|
138 | |
---|
139 | class GroupConfirmationCycle(models.Model): |
---|
140 | name = models.CharField(max_length=30) |
---|
141 | slug = models.SlugField() |
---|
142 | create_date = models.DateTimeField(default=datetime.datetime.now) |
---|
143 | |
---|
144 | def __unicode__(self, ): |
---|
145 | return u"GroupConfirmationCycle %d: %s" % (self.id, self.name, ) |
---|
146 | |
---|
147 | @classmethod |
---|
148 | def latest(cls, ): |
---|
149 | return cls.objects.order_by('-create_date')[0] |
---|
150 | |
---|
151 | |
---|
152 | class GroupMembershipUpdate(models.Model): |
---|
153 | update_time = models.DateTimeField(default=datetime.datetime.utcfromtimestamp(0)) |
---|
154 | updater_name = models.CharField(max_length=30) |
---|
155 | updater_title = models.CharField(max_length=30, help_text="You need not hold any particular title in the group, but we like to know who is completing the form.") |
---|
156 | |
---|
157 | group = models.ForeignKey(groups.models.Group, help_text="If your group does not appear in the list above, then please email asa-exec@mit.edu.") |
---|
158 | group_email = models.EmailField(help_text="The text of the law will be automatically distributed to your members via this list, in order to comply with the law.") |
---|
159 | officer_email = models.EmailField() |
---|
160 | |
---|
161 | membership_definition = models.TextField() |
---|
162 | num_undergrads = models.IntegerField() |
---|
163 | num_grads = models.IntegerField() |
---|
164 | num_alum = models.IntegerField() |
---|
165 | num_other_affiliate = models.IntegerField() |
---|
166 | num_other = models.IntegerField() |
---|
167 | |
---|
168 | membership_list = models.TextField(help_text="Member emails on separate lines (Athena usernames where applicable)") |
---|
169 | |
---|
170 | email_preface = models.TextField(blank=True, help_text="If you would like, you may add text here that will preface the text of the policies when it is sent out to the group membership list provided above.") |
---|
171 | |
---|
172 | hazing_statement = "By checking this, I hereby affirm that I have read and understand Chapter 269: Sections 17, 18, and 19 of Massachusetts Law. I furthermore attest that I have provided the appropriate address or will otherwise distribute to group members, pledges, and/or applicants, copies of Massachusetts Law 269: 17, 18, 19 and that our organization, group, or team agrees to comply with the provisions of that law. (See below for text.)" |
---|
173 | no_hazing = models.BooleanField(help_text=hazing_statement) |
---|
174 | |
---|
175 | discrimination_statement = "By checking this, I hereby affirm that I have read and understand the MIT Non-Discrimination Policy. I furthermore attest that our organization, group, or team agrees to not discriminate against individuals on the basis of race, color, sex, sexual orientation, gender identity, religion, disability, age, genetic information, veteran status, ancestry, or national or ethnic origin." |
---|
176 | no_discrimination = models.BooleanField(help_text=discrimination_statement) |
---|
177 | |
---|
178 | def __unicode__(self, ): |
---|
179 | return "GroupMembershipUpdate for %s" % (self.group, ) |
---|
180 | |
---|
181 | |
---|
182 | VALID_UNSET = 0 |
---|
183 | VALID_AUTOVALIDATED = 10 |
---|
184 | VALID_OVERRIDDEN = 20 # confirmed by an admin |
---|
185 | VALID_AUTOREJECTED = -10 |
---|
186 | VALID_HANDREJECTED = -20 |
---|
187 | VALID_CHOICES = ( |
---|
188 | (VALID_UNSET, "unvalidated"), |
---|
189 | (VALID_AUTOVALIDATED, "autovalidated"), |
---|
190 | (VALID_OVERRIDDEN, "hand-validated"), |
---|
191 | (VALID_AUTOREJECTED, "autorejected"), |
---|
192 | (VALID_HANDREJECTED, "hand-rejected"), |
---|
193 | ) |
---|
194 | |
---|
195 | class PersonMembershipUpdate(models.Model): |
---|
196 | update_time = models.DateTimeField(default=datetime.datetime.utcfromtimestamp(0)) |
---|
197 | username = models.CharField(max_length=30) |
---|
198 | cycle = models.ForeignKey(GroupConfirmationCycle) |
---|
199 | deleted = models.DateTimeField(default=None, null=True, blank=True, ) |
---|
200 | valid = models.IntegerField(choices=VALID_CHOICES, default=VALID_UNSET) |
---|
201 | groups = models.ManyToManyField(groups.models.Group, help_text="By selecting a group here, you indicate that you are an active member of the group in question.<br>If your group does not appear in the list above, then please email asa-exec@mit.edu.<br>") |
---|
202 | |
---|
203 | def __unicode__(self, ): |
---|
204 | return "PersonMembershipUpdate for %s" % (self.username, ) |
---|