Skip to content

Commit 5f1a856

Browse files
committed
* askbot/conf/group_settings.py:
- adds setting PER_EMAIL_DOMAIN_GROUPS_ENABLED - adds setting PER_EMAIL_DOMAIN_GROUP_DEFAULT_VISIBILITY - visibility of groups created for email domains options: 0 - admins, 1 - mods, 2 - members + admins and mods, 3 - public (default) * askbot/const/__init__.py: - adds constants for group visibility, PEP8 * askbot/models/analytics.py: - adds models for daily summaries for users and groups - models BaseSummary (abstract), DailySummary (abstract), UserDailySummary, GroupDailySummary * askbot/models/user.py: - adds function get_organization_name_from_domain - adds visibility field to Askbot Group * adds management command askbot_create_per_email_domain_groups * migration 0028: - adds UserDailySummary and GroupDailySummary models * migration 0029: - adds visibility field to the Askbot Group model todo: add tests for the askbot_create_per_email_domain_groups, Group.objects.get_or_create
1 parent 1a12ac8 commit 5f1a856

File tree

8 files changed

+196
-17
lines changed

8 files changed

+196
-17
lines changed

Diff for: askbot/conf/group_settings.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Group settings"""
22
from django.utils.translation import gettext_lazy as _
3+
from livesettings import values as livesettings
34
from askbot.conf.settings_wrapper import settings
45
from askbot.conf.super_groups import LOGIN_USERS_COMMUNICATION
5-
from livesettings import values as livesettings
6+
from askbot import const
67

78
GROUP_SETTINGS = livesettings.ConfigurationGroup(
89
'GROUP_SETTINGS',
@@ -64,3 +65,24 @@ def group_name_update_callback(old_name, new_name):
6465
6566
)
6667
)
68+
69+
settings.register(
70+
livesettings.BooleanValue(
71+
GROUP_SETTINGS,
72+
'PER_EMAIL_DOMAIN_GROUPS_ENABLED',
73+
default=False,
74+
description=_('Enable per email domain user groups'),
75+
help_text=_('If enabled, groups will be created for each email domain name')
76+
)
77+
)
78+
79+
settings.register(
80+
livesettings.StringValue(
81+
GROUP_SETTINGS,
82+
'PER_EMAIL_DOMAIN_GROUP_DEFAULT_VISIBILITY',
83+
choices=const.GROUP_VISIBILITY_CHOICES,
84+
default=const.GROUP_VISIBILITY_PUBLIC,
85+
description=_('Default visibility of groups created for the email domains'),
86+
help_text=_('Administrators can change the visibility of these groups individually later')
87+
)
88+
)

Diff for: askbot/const/__init__.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,11 @@
179179
TAG_CHARS = r'\wp{M}+.#-'
180180
TAG_FIRST_CHARS = r'[\wp{M}]'
181181
TAG_FORBIDDEN_FIRST_CHARS = r'#'
182-
TAG_REGEX_BARE = r'%s[%s]+' % (TAG_FIRST_CHARS, TAG_CHARS)
183-
TAG_REGEX = r'^%s$' % TAG_REGEX_BARE
182+
TAG_REGEX_BARE = rf'{TAG_FIRST_CHARS}[{TAG_CHARS}]+'
183+
TAG_REGEX = rf'^{TAG_REGEX_BARE}$'
184184

185185
TAG_STRIP_CHARS = ', '
186-
TAG_SPLIT_REGEX = r'[%s]+' % TAG_STRIP_CHARS
186+
TAG_SPLIT_REGEX = rf'[{TAG_STRIP_CHARS}]+'
187187
TAG_SEP = ',' # has to be valid TAG_SPLIT_REGEX char and MUST NOT be in const.TAG_CHARS
188188
#!!! see const.message_keys.TAG_WRONG_CHARS_MESSAGE
189189

@@ -649,3 +649,15 @@
649649
"""
650650

651651
PROFILE_WEBSITE_URL_MAX_LENGTH = 200
652+
653+
GROUP_VISIBILITY_ADMINS = 0
654+
GROUP_VISIBILITY_MODS = 1
655+
GROUP_VISIBILITY_MEMBERS = 2
656+
GROUP_VISIBILITY_PUBLIC = 3
657+
658+
GROUP_VISIBILITY_CHOICES = (
659+
(GROUP_VISIBILITY_ADMINS, _('Visible to administrators')),
660+
(GROUP_VISIBILITY_MODS, _('Visible to moderators and administrators')),
661+
(GROUP_VISIBILITY_MEMBERS, _('Visible to own members, moderators and administrators')),
662+
(GROUP_VISIBILITY_PUBLIC, _('Visible to everyone')),
663+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""A management command that creates groups for each email domain in the database."""
2+
from django.core.management.base import BaseCommand
3+
from askbot.conf import settings
4+
from askbot.models import User
5+
from askbot.models.analytics import get_organization_domains
6+
from askbot.models.user import get_organization_name_from_domain
7+
from askbot.utils.console import ProgressBar
8+
9+
class Command(BaseCommand): # pylint: disable=missing-docstring
10+
help = 'Create groups for each email domain in the database.'
11+
12+
def handle(self, *args, **options): # pylint: disable=missing-docstring, unused-argument
13+
"""Obtains a list of unique email domains names.
14+
Creates a group for each domain name, if such group does not exist.
15+
Group visibility is set to the value of settings.PER_EMAIL_DOMAIN_GROUP_DEFAULT_VISIBILITY.
16+
"""
17+
domains = get_organization_domains()
18+
count = len(domains)
19+
message = 'Initializing groups by the email address domain names'
20+
for domain in ProgressBar(domains, count, message):
21+
organization_name = get_organization_name_from_domain(domain)
22+
group = User.objects.get_or_create_group(
23+
organization_name,
24+
visibility=settings.PER_EMAIL_DOMAIN_GROUP_DEFAULT_VISIBILITY
25+
)
26+
print('Group {0} created.'.format(group.name))
27+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Generated by Django 4.2.4 on 2024-06-24 21:15
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
('askbot', '0027_populate_analytics_events'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='UserDailySummary',
18+
fields=[
19+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('num_questions', models.PositiveIntegerField()),
21+
('num_answers', models.PositiveIntegerField()),
22+
('num_upvotes', models.PositiveIntegerField()),
23+
('num_downvotes', models.PositiveIntegerField()),
24+
('question_views', models.PositiveIntegerField()),
25+
('time_on_site', models.DurationField()),
26+
('date', models.DateField(db_index=True)),
27+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
28+
],
29+
options={
30+
'abstract': False,
31+
},
32+
),
33+
migrations.CreateModel(
34+
name='GroupDailySummary',
35+
fields=[
36+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
37+
('num_questions', models.PositiveIntegerField()),
38+
('num_answers', models.PositiveIntegerField()),
39+
('num_upvotes', models.PositiveIntegerField()),
40+
('num_downvotes', models.PositiveIntegerField()),
41+
('question_views', models.PositiveIntegerField()),
42+
('time_on_site', models.DurationField()),
43+
('date', models.DateField(db_index=True)),
44+
('num_users', models.PositiveIntegerField()),
45+
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='askbot.group')),
46+
],
47+
options={
48+
'abstract': False,
49+
},
50+
),
51+
]

Diff for: askbot/migrations/0029_group_visibility.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.4 on 2024-06-24 21:17
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('askbot', '0028_userdailysummary_groupdailysummary'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='group',
15+
name='visibility',
16+
field=models.SmallIntegerField(choices=[(0, 'Visible to administrators'), (1, 'Visible to moderators and administrators'), (2, 'Visible to own members, moderators and administrators'), (3, 'Visible to everyone')], default=3),
17+
),
18+
]

Diff for: askbot/models/analytics.py

+38-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.contrib.contenttypes.fields import GenericForeignKey
99
from django.conf import settings as django_settings
1010
from django.utils.translation import gettext_lazy as _
11+
from askbot.models.user import Group as AskbotGroup
1112

1213
#for convenience, here are the activity types from used in the Activity object
1314
#TYPE_ACTIVITY_ASK_QUESTION = 1
@@ -118,6 +119,7 @@ def __str__(self):
118119
email = self.user.email # pylint: disable=no-member
119120
return f"Session: {email} {created_at} - {updated_at}"
120121

122+
121123
class Event(models.Model):
122124
"""Analytics event"""
123125
session = models.ForeignKey(Session, on_delete=models.CASCADE)
@@ -129,4 +131,39 @@ class Event(models.Model):
129131

130132
def __str__(self):
131133
timestamp = self.timestamp.isoformat() # pylint: disable=no-member
132-
return f"Event: {self.event_type_display} {timestamp}"
134+
return f"Event: {self.event_type_display} {timestamp}" # pylint: disable=no-member
135+
136+
137+
class BaseSummary(models.Model):
138+
"""
139+
An abstract model for per-interval summaries.
140+
An interval name is defined in the subclass.
141+
"""
142+
num_questions = models.PositiveIntegerField()
143+
num_answers = models.PositiveIntegerField()
144+
num_upvotes = models.PositiveIntegerField()
145+
num_downvotes = models.PositiveIntegerField()
146+
question_views = models.PositiveIntegerField()
147+
time_on_site = models.DurationField()
148+
149+
class Meta: # pylint: disable=too-few-public-methods, missing-class-docstring
150+
abstract = True
151+
152+
153+
class DailySummary(BaseSummary):
154+
"""An abstract class for daily summaries."""
155+
date = models.DateField(db_index=True)
156+
157+
class Meta: # pylint: disable=too-few-public-methods, missing-class-docstring
158+
abstract = True
159+
160+
161+
class UserDailySummary(DailySummary):
162+
"""User summary for each day with activity."""
163+
user = models.ForeignKey(User, on_delete=models.CASCADE)
164+
165+
166+
class GroupDailySummary(DailySummary):
167+
"""Group summary for each day with activity."""
168+
group = models.ForeignKey(AskbotGroup, on_delete=models.CASCADE)
169+
num_users = models.PositiveIntegerField()

Diff for: askbot/models/user.py

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
import datetime
22
import logging
3-
import re
3+
from collections import defaultdict
44
from django.db import models
5-
from django.db.models import Q
65
from django.db.utils import IntegrityError
76
from django.contrib.contenttypes.models import ContentType
87
from django.contrib.contenttypes import fields
98
from django.contrib.auth.models import User
109
from django.contrib.auth.models import Group as AuthGroup
1110
from django.core import exceptions
12-
from django.forms import EmailField, URLField
11+
from django.forms import EmailField
1312
from django.utils import translation, timezone
1413
from django.utils.translation import gettext as _
1514
from django.utils.translation import gettext_lazy
16-
from django.utils.html import strip_tags
1715
from askbot import const
1816
from askbot.conf import settings as askbot_settings
1917
from askbot.utils import functions
2018
from askbot.models.base import BaseQuerySetManager
21-
from askbot.models.analytics import Session
22-
from collections import defaultdict
19+
from askbot.models.tag import get_tags_by_names, Tag
2320

2421
PERSONAL_GROUP_NAME_PREFIX = '_personal_'
2522

23+
def get_organization_name_from_domain(domain):
24+
"""Returns organization name from domain.
25+
The organization name is the second level domain name,
26+
sentence-cased.
27+
"""
28+
base_domain = domain.split('.')[-2]
29+
return base_domain.capitalize()
30+
2631
class InvitedModerator(object):
2732
"""Mock user class to represent invited moderators"""
2833
def __init__(self, username, email):
@@ -606,6 +611,9 @@ class Group(AuthGroup):
606611
can_upload_images = models.BooleanField(default=False)
607612

608613
openness = models.SmallIntegerField(default=CLOSED, choices=OPENNESS_CHOICES)
614+
visibility = models.SmallIntegerField(default=const.GROUP_VISIBILITY_PUBLIC,
615+
choices=const.GROUP_VISIBILITY_CHOICES)
616+
609617
# preapproved email addresses and domain names to auto-join groups
610618
# trick - the field is padded with space and all tokens are space separated
611619
preapproved_emails = models.TextField(
@@ -710,7 +718,8 @@ def save(self, *args, **kwargs):
710718
super(Group, self).save(*args, **kwargs)
711719

712720

713-
class BulkTagSubscriptionManager(BaseQuerySetManager):
721+
class BulkTagSubscriptionManager(BaseQuerySetManager): # pylint: disable=too-few-public-methods
722+
"""Manager class for the BulkTagSubscription model"""
714723

715724
def create(
716725
self,
@@ -730,15 +739,13 @@ def create(
730739
tag_name_list = []
731740

732741
if tag_names:
733-
from askbot.models.tag import get_tags_by_names
734742
tags, new_tag_names = get_tags_by_names(tag_names, language_code)
735743
if new_tag_names:
736744
assert(tag_author)
737745

738746
tags_id_list= [tag.id for tag in tags]
739747
tag_name_list = [tag.name for tag in tags]
740748

741-
from askbot.models.tag import Tag
742749
new_tags = Tag.objects.create_in_bulk(
743750
tag_names=new_tag_names,
744751
user=tag_author,
@@ -771,6 +778,7 @@ def create(
771778

772779

773780
class BulkTagSubscription(models.Model):
781+
"""Subscribes users in bulk to a list of tags"""
774782
date_added = models.DateField(auto_now_add=True)
775783
tags = models.ManyToManyField('Tag')
776784
users = models.ManyToManyField(User)
@@ -779,9 +787,10 @@ class BulkTagSubscription(models.Model):
779787
objects = BulkTagSubscriptionManager()
780788

781789
def tag_list(self):
782-
return [tag.name for tag in self.tags.all()]
790+
"""Returns list of tag names"""
791+
return [tag.name for tag in self.tags.all()] # pylint: disable=no-member
783792

784-
class Meta:
793+
class Meta: # pylint: disable=too-few-public-methods, missing-docstring
785794
app_label = 'askbot'
786795
ordering = ['-date_added']
787796

Diff for: askbot/tests/test_markup.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,7 @@ def test_convert_mixed_text(self):
130130
"""
131131
"""<a href="http://example.com"><div>http://example.com</div></a>
132132
"""
133-
self.assertHTMLEqual(self.conv(text), expected)
133+
import pdb
134+
pdb.set_trace()
135+
converted = self.conv(text)
136+
self.assertHTMLEqual(converted, expected)

0 commit comments

Comments
 (0)