Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
edx-platform
Commits
691d3ea8
Commit
691d3ea8
authored
Jan 30, 2013
by
Carlos Andrés Rocha
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added functionality to split each list into static segments
Also only subscribe people who are not already subscribed
parent
65a0255f
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
61 additions
and
17 deletions
+61
-17
lms/djangoapps/mailing/management/commands/mailchimp_sync.py
+61
-17
No files found.
lms/djangoapps/mailing/management/commands/mailchimp_sync.py
View file @
691d3ea8
import
logging
import
logging
import
math
import
random
import
itertools
import
itertools
from
itertools
import
chain
from
optparse
import
make_option
from
optparse
import
make_option
from
collections
import
namedtuple
from
collections
import
namedtuple
...
@@ -28,6 +31,10 @@ class Command(BaseCommand):
...
@@ -28,6 +31,10 @@ class Command(BaseCommand):
help
=
'mailchimp list id'
),
help
=
'mailchimp list id'
),
make_option
(
'--course'
,
action
=
'store'
,
dest
=
'course_id'
,
make_option
(
'--course'
,
action
=
'store'
,
dest
=
'course_id'
,
help
=
'xmodule course_id'
),
help
=
'xmodule course_id'
),
make_option
(
'--segments'
,
action
=
'store'
,
dest
=
'segments'
,
default
=
0
,
type
=
int
,
help
=
'number of static random segments to create'
),
)
)
def
parse_options
(
self
,
options
):
def
parse_options
(
self
,
options
):
...
@@ -40,10 +47,11 @@ class Command(BaseCommand):
...
@@ -40,10 +47,11 @@ class Command(BaseCommand):
if
not
options
[
'course_id'
]:
if
not
options
[
'course_id'
]:
raise
CommandError
(
'missing course id'
)
raise
CommandError
(
'missing course id'
)
return
options
[
'key'
],
options
[
'list_id'
],
options
[
'course_id'
]
return
(
options
[
'key'
],
options
[
'list_id'
],
options
[
'course_id'
],
options
[
'segments'
])
def
handle
(
self
,
*
args
,
**
options
):
def
handle
(
self
,
*
args
,
**
options
):
key
,
list_id
,
course_id
=
self
.
parse_options
(
options
)
key
,
list_id
,
course_id
,
nsegments
=
self
.
parse_options
(
options
)
log
.
info
(
'Syncronizing email list for {0}'
.
format
(
course_id
))
log
.
info
(
'Syncronizing email list for {0}'
.
format
(
course_id
))
...
@@ -55,18 +63,25 @@ class Command(BaseCommand):
...
@@ -55,18 +63,25 @@ class Command(BaseCommand):
non_subscribed
=
unsubscribed
.
union
(
cleaned
)
non_subscribed
=
unsubscribed
.
union
(
cleaned
)
enrolled
=
get_enrolled_students
(
course_id
)
enrolled
=
get_enrolled_students
(
course_id
)
active
=
enrolled
.
all
()
.
exclude
(
user__email__in
=
non_subscribed
)
data
=
get_student_data
(
course_id
,
active
)
exclude
=
subscribed
.
union
(
non_subscribed
)
to_subscribe
=
get_student_data
(
enrolled
,
exclude
=
exclude
)
log
.
info
(
len
(
to_subscribe
))
update_merge_tags
(
mailchimp
,
list_id
,
data
)
tag_names
=
set
(
chain
.
from_iterable
(
d
.
keys
()
for
d
in
to_subscribe
))
update_data
(
mailchimp
,
list_id
,
data
)
update_merge_tags
(
mailchimp
,
list_id
,
tag_names
)
subscribe_with_data
(
mailchimp
,
list_id
,
to_subscribe
)
enrolled_emails
=
set
(
enrolled
.
values_list
(
'user__email'
,
flat
=
True
))
enrolled_emails
=
set
(
enrolled
.
values_list
(
'user__email'
,
flat
=
True
))
non_enrolled_emails
=
list
(
subscribed
.
difference
(
enrolled_emails
))
non_enrolled_emails
=
list
(
subscribed
.
difference
(
enrolled_emails
))
unsubscribe
(
mailchimp
,
list_id
,
non_enrolled_emails
)
unsubscribe
(
mailchimp
,
list_id
,
non_enrolled_emails
)
subscribed
=
subscribed
.
union
(
set
(
d
[
'EMAIL'
]
for
d
in
to_subscribe
))
make_segments
(
mailchimp
,
list_id
,
nsegments
,
subscribed
)
def
connect_mailchimp
(
key
,
list_id
,
course_id
):
def
connect_mailchimp
(
key
,
list_id
,
course_id
):
mailchimp
=
MailSnake
(
key
)
mailchimp
=
MailSnake
(
key
)
...
@@ -85,7 +100,7 @@ def connect_mailchimp(key, list_id, course_id):
...
@@ -85,7 +100,7 @@ def connect_mailchimp(key, list_id, course_id):
# check that we are connecting to the correct list
# check that we are connecting to the correct list
parts
=
course_id
.
replace
(
'_'
,
' '
)
.
replace
(
'/'
,
' '
)
.
split
()
parts
=
course_id
.
replace
(
'_'
,
' '
)
.
replace
(
'/'
,
' '
)
.
split
()
count
=
sum
(
1
for
p
in
parts
if
p
in
list_name
)
count
=
sum
(
1
for
p
in
parts
if
p
in
list_name
)
if
count
!=
4
:
if
count
<
3
:
log
.
info
(
course_id
)
log
.
info
(
course_id
)
log
.
info
(
list_name
)
log
.
info
(
list_name
)
raise
CommandError
(
'course_id does not match list name'
)
raise
CommandError
(
'course_id does not match list name'
)
...
@@ -93,11 +108,13 @@ def connect_mailchimp(key, list_id, course_id):
...
@@ -93,11 +108,13 @@ def connect_mailchimp(key, list_id, course_id):
return
mailchimp
return
mailchimp
def
get_student_data
(
course_id
,
students
):
def
get_student_data
(
students
,
exclude
=
None
):
# To speed the query, we won't retrieve the full User object, only
# To speed the query, we won't retrieve the full User object, only
# two of its values. The namedtuple simulates the User object.
# two of its values. The namedtuple simulates the User object.
FakeUser
=
namedtuple
(
'Fake'
,
'id username'
)
FakeUser
=
namedtuple
(
'Fake'
,
'id username'
)
exclude
=
exclude
if
exclude
else
set
()
def
make
(
v
):
def
make
(
v
):
e
=
{
'EMAIL'
:
v
[
'user__email'
],
e
=
{
'EMAIL'
:
v
[
'user__email'
],
'FULLNAME'
:
v
[
'name'
]
.
title
()}
'FULLNAME'
:
v
[
'name'
]
.
title
()}
...
@@ -109,7 +126,8 @@ def get_student_data(course_id, students):
...
@@ -109,7 +126,8 @@ def get_student_data(course_id, students):
fields
=
'user__email'
,
'name'
,
'user_id'
,
'user__username'
fields
=
'user__email'
,
'name'
,
'user_id'
,
'user__username'
values
=
students
.
values
(
*
fields
)
values
=
students
.
values
(
*
fields
)
return
[
make
(
s
)
for
s
in
values
]
exclude_func
=
lambda
s
:
s
[
'user__email'
]
in
exclude
return
[
make
(
s
)
for
s
in
values
if
not
exclude_func
(
s
)]
def
get_enrolled_students
(
course_id
):
def
get_enrolled_students
(
course_id
):
...
@@ -158,17 +176,13 @@ def unsubscribe(mailchimp, list_id, emails):
...
@@ -158,17 +176,13 @@ def unsubscribe(mailchimp, list_id, emails):
log
.
debug
(
result
)
log
.
debug
(
result
)
def
update_merge_tags
(
mailchimp
,
list_id
,
data
):
def
update_merge_tags
(
mailchimp
,
list_id
,
tag_names
):
names
=
set
()
for
row
in
data
:
names
.
update
(
row
.
keys
())
mc_vars
=
mailchimp
.
listMergeVars
(
id
=
list_id
)
mc_vars
=
mailchimp
.
listMergeVars
(
id
=
list_id
)
mc_names
=
set
(
v
[
'name'
]
for
v
in
mc_vars
)
mc_names
=
set
(
v
[
'name'
]
for
v
in
mc_vars
)
mc_merge
=
mailchimp
.
listMergeVarAdd
mc_merge
=
mailchimp
.
listMergeVarAdd
for
name
in
names
:
for
name
in
tag_
names
:
tag
=
name_to_tag
(
name
)
tag
=
name_to_tag
(
name
)
# verify FULLNAME is present
# verify FULLNAME is present
...
@@ -192,9 +206,9 @@ def update_merge_tags(mailchimp, list_id, data):
...
@@ -192,9 +206,9 @@ def update_merge_tags(mailchimp, list_id, data):
log
.
debug
(
result
)
log
.
debug
(
result
)
def
update_data
(
mailchimp
,
list_id
,
data
):
def
subscribe_with_data
(
mailchimp
,
list_id
,
user_
data
):
format_entry
=
lambda
e
:
{
name_to_tag
(
k
):
v
for
k
,
v
in
e
.
iteritems
()}
format_entry
=
lambda
e
:
{
name_to_tag
(
k
):
v
for
k
,
v
in
e
.
iteritems
()}
formated_data
=
list
(
format_entry
(
e
)
for
e
in
data
)
formated_data
=
list
(
format_entry
(
e
)
for
e
in
user_
data
)
# send the updates in batches of a fixed size
# send the updates in batches of a fixed size
for
batch
in
batches
(
formated_data
,
BATCH_SIZE
):
for
batch
in
batches
(
formated_data
,
BATCH_SIZE
):
...
@@ -205,6 +219,31 @@ def update_data(mailchimp, list_id, data):
...
@@ -205,6 +219,31 @@ def update_data(mailchimp, list_id, data):
log
.
debug
(
result
)
log
.
debug
(
result
)
def
make_segments
(
mailchimp
,
list_id
,
count
,
emails
):
if
count
>
0
:
# reset segments
segments
=
mailchimp
.
listStaticSegments
(
id
=
list_id
)
for
s
in
segments
:
if
s
[
'name'
]
.
startswith
(
'random'
):
mailchimp
.
listStaticSegmentDel
(
id
=
list_id
,
seg_id
=
s
[
'id'
])
# shuffle and split emails
emails
=
list
(
emails
)
random
.
shuffle
(
emails
)
chunk_size
=
int
(
math
.
ceil
(
float
(
len
(
emails
))
/
count
))
chunks
=
list
(
chunk
(
emails
,
chunk_size
))
# create segments and add emails
for
n
in
xrange
(
count
):
name
=
'random_{0:002}'
.
format
(
n
)
seg_id
=
mailchimp
.
listStaticSegmentAdd
(
id
=
list_id
,
name
=
name
)
for
batch
in
batches
(
chunks
[
n
],
BATCH_SIZE
):
mailchimp
.
listStaticSegmentMembersAdd
(
id
=
list_id
,
seg_id
=
seg_id
,
batch
=
batch
)
def
name_to_tag
(
name
):
def
name_to_tag
(
name
):
return
(
name
[:
10
]
if
len
(
name
)
>
10
else
name
)
.
replace
(
' '
,
'_'
)
.
strip
()
return
(
name
[:
10
]
if
len
(
name
)
>
10
else
name
)
.
replace
(
' '
,
'_'
)
.
strip
()
...
@@ -212,3 +251,8 @@ def name_to_tag(name):
...
@@ -212,3 +251,8 @@ def name_to_tag(name):
def
batches
(
iterable
,
size
):
def
batches
(
iterable
,
size
):
slices
=
range
(
0
,
len
(
iterable
),
size
)
slices
=
range
(
0
,
len
(
iterable
),
size
)
return
[
iterable
[
slice
(
i
,
i
+
size
)]
for
i
in
slices
]
return
[
iterable
[
slice
(
i
,
i
+
size
)]
for
i
in
slices
]
def
chunk
(
l
,
n
):
for
i
in
xrange
(
0
,
len
(
l
),
n
):
yield
l
[
i
:
i
+
n
]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment