Skip to content

Commit d9dbec8

Browse files
authored
Merge pull request #107 from eduNEXT/and/juniper_compatibility
Add django 2.2 compatibility
2 parents 8b510fb + c189a2c commit d9dbec8

File tree

19 files changed

+290
-25
lines changed

19 files changed

+290
-25
lines changed

eox_core/api/data/v1/filters.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
from django.contrib.auth.models import User
66
from edx_proctoring.models import ProctoredExamStudentAttempt # pylint: disable=import-error
77
from opaque_keys.edx.keys import CourseKey # pylint: disable=import-error
8-
from rest_framework import filters
98

109
from eox_core.edxapp_wrapper.certificates import get_generated_certificate
1110
from eox_core.edxapp_wrapper.users import get_course_enrollment
1211

1312

14-
class BaseDataApiFilter(filters.FilterSet):
13+
class BaseDataApiFilter(django_filters.rest_framework.FilterSet):
1514
"""
1615
TODO: add me
1716
"""

eox_core/api/data/v1/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from .routers import ROUTER
77
from .views import CeleryTasksStatus
88

9+
app_name = 'eox_core' # pylint: disable=invalid-name
10+
911
urlpatterns = [ # pylint: disable=invalid-name
10-
url(r'^v1/', include(ROUTER.urls, namespace='eox-data-api-v1')),
12+
url(r'^v1/', include((ROUTER.urls, 'eox_core'), namespace='eox-data-api-v1')),
1113
url(r'^v1/tasks/(?P<task_id>.*)$', CeleryTasksStatus.as_view(), name="celery-data-api-tasks"),
1214
]

eox_core/api/data/v1/viewsets.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
import six
88
from django.conf import settings
99
from django.contrib.auth.models import User
10-
from django.core.urlresolvers import reverse
1110
from django.db.models import Q
11+
from django.urls import reverse
12+
from django_filters import rest_framework as filters
1213
from edx_proctoring.models import ProctoredExamStudentAttempt # pylint: disable=import-error
13-
from rest_framework import filters, mixins, status, viewsets
14+
from rest_framework import mixins, status, viewsets
1415
from rest_framework.authentication import SessionAuthentication
1516
from rest_framework.permissions import IsAdminUser
1617
from rest_framework.response import Response

eox_core/api/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from django.conf.urls import include, url
44

5+
app_name = 'eox_core' # pylint: disable=invalid-name
6+
57
urlpatterns = [ # pylint: disable=invalid-name
68
url(r'^v1/', include('eox_core.api.v1.urls', namespace='eox-api')),
79
]

eox_core/api/v1/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from eox_core.api.v1 import views
77

8+
app_name = 'eox_core' # pylint: disable=invalid-name
9+
810
urlpatterns = [ # pylint: disable=invalid-name
911
url(r'^user/$', views.EdxappUser.as_view(), name='edxapp-user'),
1012
url(r'^enrollment/$', views.EdxappEnrollment.as_view(), name='edxapp-enrollment'),

eox_core/apps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class EoxCoreConfig(AppConfig):
2323
'lms.djangoapp': {
2424
'test': {'relative_path': 'settings.test'},
2525
'common': {'relative_path': 'settings.common'},
26-
'aws': {'relative_path': 'settings.aws'},
26+
'production': {'relative_path': 'settings.production'},
2727
'devstack': {'relative_path': 'settings.devstack'},
2828
},
2929
},
@@ -47,7 +47,7 @@ class EoxCoreCMSConfig(EoxCoreConfig):
4747
'cms.djangoapp': {
4848
'test': {'relative_path': 'settings.test'},
4949
'common': {'relative_path': 'settings.common'},
50-
'aws': {'relative_path': 'settings.aws'},
50+
'production': {'relative_path': 'settings.production'},
5151
},
5252
},
5353
}

eox_core/cms/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from eox_core.cms.views import course_team, get_courses_by_course_regex, management_view
66

7+
app_name = 'eox_core' # pylint: disable=invalid-name
8+
79
urlpatterns = [ # pylint: disable=invalid-name
810
url(r'^courses$', management_view, name='management-courses-view'),
911
url(r'^course-team-management$', course_team, name='course-team-management'),
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
Backend for the create_edxapp_user that works under the open-release/juniper.master tag
5+
"""
6+
from __future__ import absolute_import, unicode_literals
7+
8+
import logging
9+
10+
from django.conf import settings
11+
from django.contrib.auth import get_user_model
12+
from django.db import transaction
13+
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY # pylint: disable=import-error
14+
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers # pylint: disable=import-error
15+
from openedx.core.djangoapps.user_api.accounts import USERNAME_MAX_LENGTH # pylint: disable=import-error,unused-import
16+
from openedx.core.djangoapps.user_api.accounts.serializers import UserReadOnlySerializer # pylint: disable=import-error
17+
from openedx.core.djangoapps.user_api.preferences import api as preferences_api # pylint: disable=import-error
18+
from openedx.core.djangoapps.user_authn.views.registration_form import ( # pylint: disable=import-error
19+
AccountCreationForm,
20+
)
21+
from rest_framework.exceptions import NotFound
22+
from student.helpers import create_or_set_user_attribute_created_on_site # pylint: disable=import-error
23+
from student.models import ( # pylint: disable=import-error
24+
LoginFailures,
25+
UserAttribute,
26+
UserProfile,
27+
UserSignupSource,
28+
create_comments_service_user,
29+
email_exists_or_retired,
30+
username_exists_or_retired,
31+
)
32+
33+
from student.helpers import do_create_account # pylint: disable=import-error; pylint: disable=import-error
34+
from student.models import CourseEnrollment # pylint: disable=import-error; pylint: disable=import-error
35+
36+
LOG = logging.getLogger(__name__)
37+
User = get_user_model() # pylint: disable=invalid-name
38+
39+
40+
def get_user_read_only_serializer():
41+
"""
42+
Great serializer that fits our needs
43+
"""
44+
return UserReadOnlySerializer
45+
46+
47+
def check_edxapp_account_conflicts(email, username):
48+
"""
49+
Exposed function to check conflicts
50+
"""
51+
conflicts = []
52+
53+
if username and username_exists_or_retired(username):
54+
conflicts.append("username")
55+
56+
if email and email_exists_or_retired(email):
57+
conflicts.append("email")
58+
59+
return conflicts
60+
61+
62+
def create_edxapp_user(*args, **kwargs):
63+
"""
64+
Creates a user on the open edx django site using calls to
65+
functions defined in the edx-platform codebase
66+
67+
Example call:
68+
69+
data = {
70+
'email': "address@example.org",
71+
'username': "Username",
72+
'password': "P4ssW0rd",
73+
'fullname': "Full Name",
74+
'activate': True,
75+
'site': request.site,
76+
'language_preference': 'es-419',
77+
}
78+
user = create_edxapp_user(**data)
79+
80+
"""
81+
errors = []
82+
83+
email = kwargs.pop("email")
84+
username = kwargs.pop("username")
85+
conflicts = check_edxapp_account_conflicts(email=email, username=username)
86+
if conflicts:
87+
return None, ["Fatal: account collition with the provided: {}".format(", ".join(conflicts))]
88+
89+
data = {
90+
'username': username,
91+
'email': email,
92+
'password': kwargs.pop("password"),
93+
'name': kwargs.pop("fullname"),
94+
}
95+
# Go ahead and create the new user
96+
with transaction.atomic():
97+
# In theory is possible to extend the registration form with a custom app
98+
# An example form app for this can be found at http://github.com/open-craft/custom-form-app
99+
# form = get_registration_extension_form(data=params)
100+
# if not form:
101+
form = AccountCreationForm(
102+
data=data,
103+
tos_required=False,
104+
# TODO: we need to support the extra profile fields as defined in the django.settings
105+
# extra_fields=extra_fields,
106+
# extended_profile_fields=extended_profile_fields,
107+
# enforce_password_policy=enforce_password_policy,
108+
)
109+
(user, profile, registration) = do_create_account(form) # pylint: disable=unused-variable
110+
111+
site = kwargs.pop("site", False)
112+
if site:
113+
create_or_set_user_attribute_created_on_site(user, site)
114+
else:
115+
errors.append("The user was not assigned to any site")
116+
117+
try:
118+
create_comments_service_user(user)
119+
except Exception: # pylint: disable=broad-except
120+
errors.append("No comments_service_user was created")
121+
122+
# TODO: link account with third party auth
123+
124+
lang_pref = kwargs.pop("language_preference", False)
125+
if lang_pref:
126+
try:
127+
preferences_api.set_user_preference(user, LANGUAGE_KEY, lang_pref)
128+
except Exception: # pylint: disable=broad-except
129+
errors.append("Could not set lang preference '{} for user '{}'".format(
130+
lang_pref,
131+
user.username,
132+
))
133+
134+
if kwargs.pop("activate_user", False):
135+
user.is_active = True
136+
user.save()
137+
138+
# TODO: run conditional email sequence
139+
140+
return user, errors
141+
142+
143+
def get_edxapp_user(**kwargs):
144+
"""
145+
Retrieve an user by username and/or email
146+
147+
The user will be returned only if it belongs to the calling site
148+
149+
Examples:
150+
>>> get_edxapp_user(
151+
{
152+
"username": "Bob",
153+
"site": request.site
154+
}
155+
)
156+
>>> get_edxapp_user(
157+
{
158+
"email": "Bob@mailserver.com",
159+
"site": request.site
160+
}
161+
)
162+
"""
163+
params = {key: kwargs.get(key) for key in ['username', 'email'] if key in kwargs}
164+
site = kwargs.get('site')
165+
try:
166+
domain = site.domain
167+
except AttributeError:
168+
domain = None
169+
170+
try:
171+
user = User.objects.get(**params)
172+
for source_method in FetchUserSiteSources.get_enabled_source_methods():
173+
if source_method(user, domain):
174+
break
175+
else:
176+
raise User.DoesNotExist
177+
except User.DoesNotExist:
178+
raise NotFound('No user found by {query} on site {site}.'.format(query=str(params), site=domain))
179+
return user
180+
181+
182+
def get_course_team_user(*args, **kwargs):
183+
"""
184+
Get _course_team_user function.
185+
We need to check if the SERVICE_VARIANT is equal to cms, since
186+
contentstore is a module registered in the INSTALLED_APPS
187+
of the cms only.
188+
"""
189+
if settings.SERVICE_VARIANT == 'cms':
190+
from contentstore.views.user import _course_team_user # pylint: disable=import-error
191+
return _course_team_user(*args, **kwargs)
192+
return None
193+
194+
195+
class FetchUserSiteSources(object):
196+
"""
197+
Methods to make the comparison to check if an user belongs to a site plus the
198+
get_enabled_source_methods that just brings an array of functions enabled to do so
199+
"""
200+
201+
@classmethod
202+
def get_enabled_source_methods(cls):
203+
""" Brings the array of methods to check if an user belongs to a site. """
204+
sources = configuration_helpers.get_value(
205+
'EOX_CORE_USER_ORIGIN_SITE_SOURCES',
206+
getattr(settings, 'EOX_CORE_USER_ORIGIN_SITE_SOURCES')
207+
)
208+
return [getattr(cls, source) for source in sources]
209+
210+
@staticmethod
211+
def fetch_from_created_on_site_prop(user, domain):
212+
""" Fetch option. """
213+
if not domain:
214+
return False
215+
return UserAttribute.get_user_attribute(user, 'created_on_site') == domain
216+
217+
@staticmethod
218+
def fetch_from_user_signup_source(user, domain):
219+
""" Read the signup source. """
220+
return len(UserSignupSource.objects.filter(user=user, site=domain)) > 0
221+
222+
@staticmethod
223+
def fetch_from_unfiltered_table(user, site):
224+
""" Fetch option that does not take into account the multi-tentancy model of the installation. """
225+
return bool(user)
226+
227+
228+
def get_course_enrollment():
229+
""" get CourseEnrollment model """
230+
return CourseEnrollment
231+
232+
233+
def get_user_signup_source():
234+
""" get UserSignupSource model """
235+
return UserSignupSource
236+
237+
238+
def get_login_failures():
239+
""" get LoginFailures model """
240+
return LoginFailures
241+
242+
243+
def get_user_profile():
244+
""" Gets the UserProfile model """
245+
246+
return UserProfile

eox_core/middleware.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from django.db.models.signals import post_save
1717
from django.dispatch import receiver
1818
from django.http import Http404, HttpResponseRedirect
19+
from django.utils.deprecation import MiddlewareMixin
1920

2021
from eox_core.edxapp_wrapper.configuration_helpers import get_configuration_helper
2122
from eox_core.models import Redirection
@@ -26,7 +27,7 @@
2627
LOG = logging.getLogger(__name__)
2728

2829

29-
class PathRedirectionMiddleware(object):
30+
class PathRedirectionMiddleware(MiddlewareMixin):
3031
"""
3132
Middleware to create custom responses based on the request path
3233
"""
@@ -125,7 +126,7 @@ def login_required(self, request, path, **kwargs): # pylint: disable=unused-arg
125126
Action: a user session must exist.
126127
If it does not, redirect to the login page
127128
"""
128-
if request.user.is_authenticated():
129+
if request.user.is_authenticated:
129130
return None
130131
resolved_login_url = configuration_helper.get_dict("FEATURES", {}).get(
131132
"ednx_custom_login_link", settings.LOGIN_URL)
@@ -141,14 +142,14 @@ def not_found_loggedin(self, request, **kwargs): # pylint: disable=unused-argum
141142
"""
142143
Action: return 404 error for users which have a session
143144
"""
144-
if request.user.is_authenticated():
145+
if request.user.is_authenticated:
145146
raise Http404
146147

147148
def not_found_loggedout(self, request, **kwargs): # pylint: disable=unused-argument
148149
"""
149150
Action: return 404 error for users that don't have a session
150151
"""
151-
if not request.user.is_authenticated():
152+
if not request.user.is_authenticated:
152153
raise Http404
153154

154155
def redirect_always(self, key, values, **kwargs): # pylint: disable=unused-argument
@@ -161,20 +162,20 @@ def redirect_loggedin(self, request, key, values, **kwargs): # pylint: disable=
161162
"""
162163
Action: redirect logged users to the given target
163164
"""
164-
if request.user.is_authenticated():
165+
if request.user.is_authenticated:
165166
return HttpResponseRedirect(values[key])
166167
return None
167168

168169
def redirect_loggedout(self, request, key, values, **kwargs): # pylint: disable=unused-argument
169170
"""
170171
Action: redirect external visitors to the given target
171172
"""
172-
if not request.user.is_authenticated():
173+
if not request.user.is_authenticated:
173174
return HttpResponseRedirect(values[key])
174175
return None
175176

176177

177-
class RedirectionsMiddleware(object):
178+
class RedirectionsMiddleware(MiddlewareMixin):
178179
"""
179180
Middleware for Redirecting microsites to other domains or to error pages
180181
"""

0 commit comments

Comments
 (0)