32 |
102 | {% endblock %}
103 |
--------------------------------------------------------------------------------
/apps/fok/static/styles/scss/components/_sidenav.scss:
--------------------------------------------------------------------------------
1 | .sidenav {
2 | position: fixed;
3 | width: $sidenav-width;
4 | left: 0;
5 | top: 0;
6 | margin: 0;
7 | transform: translateX(-100%);
8 | height: 100%;
9 | height: calc(100% + 60px);
10 | height: -moz-calc(100%); //Temporary Firefox Fix
11 | padding-bottom: 60px;
12 | background-color: $sidenav-bg-color;
13 | z-index: 999;
14 | overflow-y: auto;
15 | will-change: transform;
16 | backface-visibility: hidden;
17 | transform: translateX(-105%);
18 |
19 | @extend .z-depth-1;
20 |
21 | // Right Align
22 | &.right-aligned {
23 | right: 0;
24 | transform: translateX(105%);
25 | left: auto;
26 | transform: translateX(100%);
27 | }
28 |
29 | .collapsible {
30 | margin: 0;
31 | }
32 |
33 |
34 | li {
35 | float: none;
36 | line-height: $sidenav-line-height;
37 |
38 | &.active { background-color: rgba(0,0,0,.05); }
39 | }
40 |
41 | li > a {
42 | color: $sidenav-font-color;
43 | display: block;
44 | font-size: $sidenav-font-size;
45 | font-weight: 500;
46 | height: $sidenav-item-height;
47 | line-height: $sidenav-line-height;
48 | padding: 0 ($sidenav-padding * 2);
49 |
50 | &:hover { background-color: rgba(0,0,0,.05);}
51 |
52 | &.btn, &.btn-large, &.btn-flat, &.btn-floating {
53 | margin: 10px 15px;
54 | }
55 |
56 | &.btn,
57 | &.btn-large,
58 | &.btn-floating { color: $button-raised-color; }
59 | &.btn-flat { color: $button-flat-color; }
60 |
61 | &.btn:hover,
62 | &.btn-large:hover { background-color: lighten($button-raised-background, 5%); }
63 | &.btn-floating:hover { background-color: $button-raised-background; }
64 |
65 | & > i,
66 | & > [class^="mdi-"], li > a > [class*="mdi-"],
67 | & > i.material-icons {
68 | float: left;
69 | height: $sidenav-item-height;
70 | line-height: $sidenav-line-height;
71 | margin: 0 ($sidenav-padding * 2) 0 0;
72 | width: $sidenav-item-height / 2;
73 | color: rgba(0,0,0,.54);
74 | }
75 | }
76 |
77 |
78 | .divider {
79 | margin: ($sidenav-padding / 2) 0 0 0;
80 | }
81 |
82 | .subheader {
83 | &:hover {
84 | background-color: transparent;
85 | }
86 |
87 | cursor: initial;
88 | pointer-events: none;
89 | color: rgba(0,0,0,.54);
90 | font-size: $sidenav-font-size;
91 | font-weight: 500;
92 | line-height: $sidenav-line-height;
93 | }
94 |
95 | .user-view {
96 | position: relative;
97 | padding: ($sidenav-padding * 2) ($sidenav-padding * 2) 0;
98 | margin-bottom: $sidenav-padding / 2;
99 |
100 | & > a {
101 | &:hover { background-color: transparent; }
102 | height: auto;
103 | padding: 0;
104 | }
105 |
106 | .background {
107 | overflow: hidden;
108 | position: absolute;
109 | top: 0;
110 | right: 0;
111 | bottom: 0;
112 | left: 0;
113 | z-index: -1;
114 | }
115 |
116 | .circle, .name, .email {
117 | display: block;
118 | }
119 |
120 | .circle {
121 | height: 64px;
122 | width: 64px;
123 | }
124 |
125 | .name,
126 | .email {
127 | font-size: $sidenav-font-size;
128 | line-height: $sidenav-line-height / 2;
129 | }
130 |
131 | .name {
132 | margin-top: 16px;
133 | font-weight: 500;
134 | }
135 |
136 | .email {
137 | padding-bottom: 16px;
138 | font-weight: 400;
139 | }
140 | }
141 | }
142 |
143 |
144 | // Touch interaction
145 | .drag-target {
146 | // Right Align
147 | &.right-aligned {
148 | right: 0;
149 | }
150 |
151 | height: 100%;
152 | width: 10px;
153 | position: fixed;
154 | top: 0;
155 | z-index: 998;
156 | }
157 |
158 |
159 | // Fixed Sidenav shown
160 | .sidenav.sidenav-fixed {
161 | // Right Align
162 | &.right-aligned {
163 | right: 0;
164 | left: auto;
165 | }
166 |
167 | left: 0;
168 | transform: translateX(0);
169 | position: fixed;
170 | }
171 |
172 | // Fixed Sidenav hide on smaller
173 | @media #{$medium-and-down} {
174 | .sidenav {
175 | &.sidenav-fixed {
176 | transform: translateX(-105%);
177 |
178 | &.right-aligned {
179 | transform: translateX(105%);
180 | }
181 | }
182 |
183 | > a {
184 | padding: 0 $sidenav-padding;
185 | }
186 |
187 | .user-view {
188 | padding: $sidenav-padding $sidenav-padding 0;
189 | }
190 | }
191 | }
192 |
193 |
194 | .sidenav .collapsible-body > ul:not(.collapsible) > li.active,
195 | .sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active {
196 | background-color: $primary-color;
197 | a {
198 | color: $sidenav-bg-color;
199 | }
200 | }
201 | .sidenav .collapsible-body {
202 | padding: 0;
203 | }
204 |
205 |
206 | .sidenav-overlay {
207 | position: fixed;
208 | top: 0;
209 | left: 0;
210 | right: 0;
211 | opacity: 0;
212 | height: 120vh;
213 | background-color: rgba(0,0,0,.5);
214 | z-index: 997;
215 | display: none;
216 | }
217 |
--------------------------------------------------------------------------------
/apps/fok/models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import AbstractUser
2 | from django.db import models
3 | from uuid import uuid4
4 | from django.contrib.auth import get_user_model
5 | from cc_lib.utils import implement_slug
6 | from django.shortcuts import reverse
7 | from django.contrib.auth.models import UserManager
8 | from constance import config as cons
9 |
10 |
11 | def upload_path(instance, filename):
12 | if isinstance(instance, Campaign):
13 | return 'campaign.image/{0}.png'.format(str(uuid4()), filename)
14 |
15 |
16 | class CCUserManager(UserManager):
17 | def create_superuser(self, email, password, **extra_fields):
18 | return super().create_superuser(email, email, password, **extra_fields)
19 |
20 |
21 | class Background(models.Model):
22 | name = models.CharField(max_length=100)
23 |
24 | def __str__(self):
25 | return self.name
26 |
27 |
28 | class User(AbstractUser):
29 | REQUIRED_FIELDS = []
30 | USERNAME_FIELD = 'id'
31 | objects = CCUserManager()
32 | username = models.CharField(max_length=30, unique=False)
33 | password = models.CharField(max_length=120, unique=False, blank=True, null=True)
34 | name = models.CharField(max_length=150, unique=False)
35 | background = models.ForeignKey(
36 | Background,
37 | null=True,
38 | blank=True,
39 | on_delete=models.SET_NULL,
40 | verbose_name='Research Field'
41 | )
42 | newsletter = models.BooleanField(default=False)
43 | is_greeted = models.BooleanField(default=False)
44 |
45 | @property
46 | def pledged_campaigns(self):
47 | return [pledge.campaign for pledge in self.pledges.all()]
48 |
49 | def __str__(self):
50 | if self.first_name and self.last_name:
51 | return f'{self.first_name} {self.last_name}'
52 | return self.name
53 |
54 |
55 | class Campaign(models.Model):
56 | title = models.CharField(max_length=500)
57 | slug = models.CharField(max_length=500, editable=False)
58 | image = models.ImageField(null=True, upload_to=upload_path)
59 | short_description = models.CharField(max_length=500, null=True, blank=True)
60 | description = models.TextField()
61 | criteria = models.TextField()
62 | visible = models.BooleanField(default=False)
63 | created_at = models.DateTimeField(auto_now=True, editable=False)
64 | position = models.IntegerField(default=0)
65 |
66 | @property
67 | def absolute_url(self):
68 | return reverse('campaign', args=[str(self.slug)])
69 |
70 | def get_pledge_from(self, user):
71 | return next(iter([pledge for pledge in self.pledges.all() if pledge.user == user]), None)
72 |
73 | @property
74 | def pledge_url(self):
75 | return reverse('pledge', args=[str(self.slug)])
76 |
77 | @property
78 | def stats(self):
79 | from collections import Counter
80 | from constance import config
81 |
82 | pledges = self.pledges.all()
83 | fields = [pledge.user.background for pledge in pledges]
84 | public_users = [str(pledge.user) for pledge in pledges if pledge.allow_public_name]
85 |
86 | n_pledges = len(pledges)
87 | n_public_users = len(public_users)
88 | n_anonymous_users = n_pledges - n_public_users
89 | n_fields = len(fields)
90 |
91 | fields_percentage = {c[0]: int(c[1]/n_fields * 100) for c in Counter(fields).most_common()}
92 | anonymous_pledges_percentage = (n_anonymous_users / n_pledges) * 100 \
93 | if n_anonymous_users != 0 \
94 | else 0
95 |
96 | support_metric = int((n_public_users + (n_anonymous_users * config.ANONYMOUS_USERS_FACTOR)) / n_pledges * 100) \
97 | if n_pledges != 0 else 0
98 |
99 | return {
100 | 'public_user_pledges': public_users,
101 | 'pledges_count': n_pledges,
102 | 'research_field_impact': fields_percentage,
103 | 'anonymous_pledges_count': n_anonymous_users,
104 | 'anonymous_pledges_percentage': f'{anonymous_pledges_percentage:.2f}',
105 | 'support_metric': f'{support_metric:.2f}' if not cons.OVERRIDE_SUPPORT_METRICS_TEXT else cons.OVERRIDE_SUPPORT_METRICS_TEXT
106 | }
107 |
108 | def __str__(self):
109 | return self.title
110 |
111 |
112 | implement_slug(Campaign, 'title')
113 |
114 |
115 | class EnabledAuthorPosition(models.Model):
116 | position = models.CharField(max_length=25)
117 |
118 | def __str__(self):
119 | return self.position
120 |
121 |
122 | class Pledge(models.Model):
123 | class Meta:
124 | ordering = ['created_at']
125 |
126 | user = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, null=True, related_name='pledges')
127 | campaign = models.ForeignKey(Campaign, on_delete=models.SET_NULL, null=True, related_name='pledges')
128 | implication = models.FloatField(verbose_name='Threshold', blank=True, default=None)
129 | author_position = models.ManyToManyField(EnabledAuthorPosition, blank=True, default=None)
130 | allow_public_name = models.BooleanField(default=False)
131 | created_at = models.DateTimeField(auto_now_add=True)
132 | updated_at = models.DateTimeField(auto_now=True)
133 |
134 | def __str__(self):
135 | return f'{str(self.user)} ({self.user.email}) pledge to {self.campaign.title}'
136 |
--------------------------------------------------------------------------------
/apps/fok/static/styles/scss/components/forms/_checkboxes.scss:
--------------------------------------------------------------------------------
1 | /* Checkboxes
2 | ========================================================================== */
3 |
4 | /* Remove default checkbox */
5 | [type="checkbox"]:not(:checked),
6 | [type="checkbox"]:checked {
7 | position: absolute;
8 | opacity: 0;
9 | pointer-events: none;
10 | }
11 |
12 | // Checkbox Styles
13 | [type="checkbox"] {
14 | // Text Label Style
15 | + span:not(.lever) {
16 | position: relative;
17 | padding-left: 35px;
18 | cursor: pointer;
19 | display: inline-block;
20 | height: 25px;
21 | line-height: 25px;
22 | font-size: 1rem;
23 | user-select: none;
24 | }
25 |
26 | /* checkbox aspect */
27 | + span:not(.lever):before,
28 | &:not(.filled-in) + span:not(.lever):after {
29 | content: '';
30 | position: absolute;
31 | top: 0;
32 | left: 0;
33 | width: 18px;
34 | height: 18px;
35 | z-index: 0;
36 | border: 2px solid $radio-empty-color;
37 | border-radius: 1px;
38 | margin-top: 3px;
39 | transition: .2s;
40 | }
41 |
42 | &:not(.filled-in) + span:not(.lever):after {
43 | border: 0;
44 | transform: scale(0);
45 | }
46 |
47 | &:not(:checked):disabled + span:not(.lever):before {
48 | border: none;
49 | background-color: $input-disabled-color;
50 | }
51 |
52 | // Focused styles
53 | &.tabbed:focus + span:not(.lever):after {
54 | transform: scale(1);
55 | border: 0;
56 | border-radius: 50%;
57 | box-shadow: 0 0 0 10px rgba(0,0,0,.1);
58 | background-color: rgba(0,0,0,.1);
59 | }
60 | }
61 |
62 | [type="checkbox"]:checked {
63 | + span:not(.lever):before {
64 | top: -4px;
65 | left: -5px;
66 | width: 12px;
67 | height: 22px;
68 | border-top: 2px solid transparent;
69 | border-left: 2px solid transparent;
70 | border-right: $radio-border;
71 | border-bottom: $radio-border;
72 | transform: rotate(40deg);
73 | backface-visibility: hidden;
74 | transform-origin: 100% 100%;
75 | }
76 |
77 | &:disabled + span:before {
78 | border-right: 2px solid $input-disabled-color;
79 | border-bottom: 2px solid $input-disabled-color;
80 | }
81 | }
82 |
83 | /* Indeterminate checkbox */
84 | [type="checkbox"]:indeterminate {
85 | + span:not(.lever):before {
86 | top: -11px;
87 | left: -12px;
88 | width: 10px;
89 | height: 22px;
90 | border-top: none;
91 | border-left: none;
92 | border-right: $radio-border;
93 | border-bottom: none;
94 | transform: rotate(90deg);
95 | backface-visibility: hidden;
96 | transform-origin: 100% 100%;
97 | }
98 |
99 | // Disabled indeterminate
100 | &:disabled + span:not(.lever):before {
101 | border-right: 2px solid $input-disabled-color;
102 | background-color: transparent;
103 | }
104 | }
105 |
106 | // Filled in Style
107 | [type="checkbox"].filled-in {
108 | // General
109 | + span:not(.lever):after {
110 | border-radius: 2px;
111 | }
112 |
113 | + span:not(.lever):before,
114 | + span:not(.lever):after {
115 | content: '';
116 | left: 0;
117 | position: absolute;
118 | /* .1s delay is for check animation */
119 | transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;
120 | z-index: 1;
121 | }
122 |
123 | // Unchecked style
124 | &:not(:checked) + span:not(.lever):before {
125 | width: 0;
126 | height: 0;
127 | border: 3px solid transparent;
128 | left: 6px;
129 | top: 10px;
130 | transform: rotateZ(37deg);
131 | transform-origin: 100% 100%;
132 | }
133 |
134 | &:not(:checked) + span:not(.lever):after {
135 | height: 20px;
136 | width: 20px;
137 | background-color: transparent;
138 | border: 2px solid $radio-empty-color;
139 | top: 0px;
140 | z-index: 0;
141 | }
142 |
143 | // Checked style
144 | &:checked {
145 | + span:not(.lever):before {
146 | top: 0;
147 | left: 1px;
148 | width: 8px;
149 | height: 13px;
150 | border-top: 2px solid transparent;
151 | border-left: 2px solid transparent;
152 | border-right: 2px solid $input-background;
153 | border-bottom: 2px solid $input-background;
154 | transform: rotateZ(37deg);
155 | transform-origin: 100% 100%;
156 | }
157 |
158 | + span:not(.lever):after {
159 | top: 0;
160 | width: 20px;
161 | height: 20px;
162 | border: 2px solid $secondary-color;
163 | background-color: $secondary-color;
164 | z-index: 0;
165 | }
166 | }
167 |
168 | // Focused styles
169 | &.tabbed:focus + span:not(.lever):after {
170 | border-radius: 2px;
171 | border-color: $radio-empty-color;
172 | background-color: rgba(0,0,0,.1);
173 | }
174 |
175 | &.tabbed:checked:focus + span:not(.lever):after {
176 | border-radius: 2px;
177 | background-color: $secondary-color;
178 | border-color: $secondary-color;
179 | }
180 |
181 | // Disabled style
182 | &:disabled:not(:checked) + span:not(.lever):before {
183 | background-color: transparent;
184 | border: 2px solid transparent;
185 | }
186 |
187 | &:disabled:not(:checked) + span:not(.lever):after {
188 | border-color: transparent;
189 | background-color: $input-disabled-solid-color;
190 | }
191 |
192 | &:disabled:checked + span:not(.lever):before {
193 | background-color: transparent;
194 | }
195 |
196 | &:disabled:checked + span:not(.lever):after {
197 | background-color: $input-disabled-solid-color;
198 | border-color: $input-disabled-solid-color;
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/apps/fok/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.7 on 2019-07-06 07:24
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 | import django.utils.timezone
7 | import fok.models
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | initial = True
13 |
14 | dependencies = [
15 | ('auth', '0009_alter_user_last_name_max_length'),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='User',
21 | fields=[
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('password', models.CharField(max_length=128, verbose_name='password')),
24 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
25 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
26 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
28 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
29 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
30 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
31 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
32 | ('username', models.CharField(max_length=20)),
33 | ('name', models.CharField(max_length=150)),
34 | ('newsletter', models.BooleanField(default=False)),
35 | ],
36 | options={
37 | 'verbose_name': 'user',
38 | 'verbose_name_plural': 'users',
39 | 'abstract': False,
40 | },
41 | managers=[
42 | ('objects', fok.models.CCUserManager()),
43 | ],
44 | ),
45 | migrations.CreateModel(
46 | name='Background',
47 | fields=[
48 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
49 | ('name', models.CharField(max_length=100)),
50 | ],
51 | ),
52 | migrations.CreateModel(
53 | name='Campaign',
54 | fields=[
55 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
56 | ('title', models.CharField(max_length=500)),
57 | ('slug', models.CharField(editable=False, max_length=500)),
58 | ('image', models.ImageField(null=True, upload_to=fok.models.upload_path)),
59 | ('short_description', models.CharField(blank=True, max_length=500, null=True)),
60 | ('description', models.TextField()),
61 | ('criteria', models.TextField()),
62 | ('visible', models.BooleanField(default=False)),
63 | ('created_at', models.DateTimeField(auto_now=True)),
64 | ('position', models.IntegerField(default=0)),
65 | ],
66 | ),
67 | migrations.CreateModel(
68 | name='EnabledAuthorPosition',
69 | fields=[
70 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
71 | ('position', models.CharField(max_length=25)),
72 | ],
73 | ),
74 | migrations.CreateModel(
75 | name='Pledge',
76 | fields=[
77 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
78 | ('implication', models.FloatField(verbose_name='Threshold')),
79 | ('allow_public_name', models.BooleanField(default=False)),
80 | ('created_at', models.DateTimeField(auto_now_add=True)),
81 | ('updated_at', models.DateTimeField(auto_now=True)),
82 | ('author_position', models.ManyToManyField(to='fok.EnabledAuthorPosition')),
83 | ('campaign', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pledges', to='fok.Campaign')),
84 | ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pledges', to=settings.AUTH_USER_MODEL)),
85 | ],
86 | options={
87 | 'ordering': ['created_at'],
88 | },
89 | ),
90 | migrations.AddField(
91 | model_name='user',
92 | name='background',
93 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='fok.Background', verbose_name='Research Field'),
94 | ),
95 | migrations.AddField(
96 | model_name='user',
97 | name='groups',
98 | field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
99 | ),
100 | migrations.AddField(
101 | model_name='user',
102 | name='user_permissions',
103 | field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
104 | ),
105 | ]
106 |
--------------------------------------------------------------------------------
/apps/fok/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% load staticfiles %}
4 | {% load section %}
5 |
6 |
7 |
8 |
Free our knowledge
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {% if messages %}
19 | {% include 'fok/partials/messages.html' %}
20 | {% endif %}
21 | {% if not user.is_anonymous and not user.is_greeted %}
22 |
23 |
24 |
25 | {% if config.TITLE_GREETINGS %}
26 |
27 |
28 | {{ config.TITLE_GREETINGS }}
29 |
30 | {% endif %}
31 | {% if config.TEXT_GREETINGS %}
{{ config.TEXT_GREETINGS }}
{% endif %}
32 |
33 |
55 | {% if config.COOKIES_TEXT %}
56 |
57 | {{ config.COOKIES_TEXT }}
58 |
59 | {% endif %}
60 |
61 |
62 |
65 |
66 |
75 | {% endif %}
76 |
112 |
113 |
114 | {% block content %}
115 | {% endblock %}
116 |
117 |
118 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/cc_lib/commands/generate_fakes_command.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | # From: https://docs.djangoproject.com/en/2.1/howto/custom-management-commands/
5 |
6 | from django.core.management.base import BaseCommand
7 | from django.core.management.commands.flush import Command as Flush
8 | from django.db import DEFAULT_DB_ALIAS
9 | from django.conf import settings
10 | from cc_lib.utils import get_class_from_route
11 | import inspect
12 | import random
13 | import factory
14 | from factory import fuzzy
15 |
16 |
17 | relationship_strategies = {
18 | 'choice': (lambda **kwargs: fuzzy.FuzzyChoice(kwargs['objects']), False),
19 | 'sample': (lambda **kwargs: random.sample(kwargs['objects'], kwargs['number']), True),
20 | 'iterate': (lambda **kwargs: factory.Iterator(kwargs['objects']), False)
21 | }
22 |
23 |
24 | class GenerateFakesCommand(BaseCommand):
25 | help = 'Generates fake data for all the models, for testing purposes.'
26 |
27 | def __init__(self, *args, **kwargs):
28 | super().__init__(*args, **kwargs)
29 | self.factories_dict = {}
30 | self.factory_by_models = {}
31 |
32 | def add_arguments(self, parser):
33 | parser.add_argument(
34 | '--flush', '--flush', action='store_true', dest='flush'
35 | )
36 |
37 | def generated_fake_data(self, objects, model, factory_obj):
38 | pass
39 |
40 | def fakes_generation_finished(self):
41 | pass
42 |
43 | def get_image_fields(self, factory_class):
44 | from django.db.models.fields.files import ImageFileDescriptor
45 | return [f[0] for f in inspect.getmembers(factory_class._meta.model) if isinstance(f[1], ImageFileDescriptor) and f[0] in factory_class._meta.declarations.keys()]
46 |
47 | def get_related_data(self, factory_cls, related):
48 | related_instances = {}
49 | lazy_related = {}
50 | if related:
51 | for field_name, values in related.items():
52 | relationship_field = getattr(factory_cls._meta.model, field_name)
53 | to_class = relationship_field.field.related_model._meta.label.split('.')[-1]
54 | assert to_class in self.factory_by_models.keys(), \
55 | f"""
56 | You have to generate {to_class} fixtures, before {cls_name} fixtures for assigning them
57 | to the {field_name} field.
58 | """
59 | related_objects = self.get_generated_objects(to_class)
60 | strategy = values.get('strategy', 'iterate')
61 | strategy, many = relationship_strategies[strategy]
62 | dest = lazy_related if many else related_instances
63 | dest[field_name] = strategy(objects=related_objects, **values)
64 | return related_instances, lazy_related
65 |
66 | def create_data(self, factory_cls, number=50, related=None, fields_value=None, field_values=None, extra_objects=None):
67 | """
68 |
69 | :param factory_cls:
70 | :param number:
71 | :param related:
72 | :param fields_value: It is an object with the same property name than the fixture. It will set that value on the created fixtures.
73 | :param field_values: It is a list of values for field names. It will iterate over those values and set them on the created fixtures.
74 | :param extra_objects:
75 | :return:
76 | """
77 | cls_name = factory_cls._meta.model.__name__
78 | related_instances, lazy_related = self.get_related_data(factory_cls, related)
79 | field_values_to_assign = {} if field_values is None else \
80 | {field_name: factory.Iterator(values) for field_name, values in field_values.items()}
81 | fields_value = {} if not fields_value else fields_value
82 | objects = factory_cls.create_batch(
83 | size=number,
84 | **fields_value,
85 | **field_values_to_assign,
86 | **related_instances
87 | )
88 | for field_name, related_objs in lazy_related.items():
89 | for obj in objects:
90 | collection = getattr(obj, field_name)
91 | collection.set(related_objs)
92 | obj.save()
93 | self.stdout.write(self.style.SUCCESS(f'Fake data ({number} objects) for {cls_name} model created.'))
94 | self.generated_fake_data(objects, cls_name, factory_cls)
95 |
96 | if extra_objects:
97 | for data in extra_objects:
98 | objects = objects + self.create_data(factory_cls, **data)
99 |
100 | return objects
101 |
102 | def download_and_upload_images(self, objects, fields):
103 | import urllib.request as request
104 | import tempfile
105 | from django.core.files import File
106 |
107 | def _download_and_upload_images(obj, prop):
108 | if getattr(obj, prop) is None:
109 | return
110 | url = str(getattr(obj, prop))
111 | response = request.urlopen(url)
112 | data = response.read()
113 | fp = tempfile.TemporaryFile()
114 | fp.write(data)
115 | fp.seek(0)
116 | setattr(obj, prop, File(fp))
117 | obj.save()
118 |
119 | for field in fields:
120 | [_download_and_upload_images(obj, field) for obj in objects]
121 | len(objects) > 0 and self.stdout.write(
122 | self.style.SUCCESS(f'Updated {field} field image for model {str(objects[0].__class__.__name__)}.')
123 | )
124 |
125 | def get_generated_objects(self, cls):
126 | related_factory = self.factory_by_models[cls]
127 | return self.factories_dict[related_factory]['objects']
128 |
129 | def get_from_yml(self, path):
130 | import yaml
131 | with open(path) as ymlfile:
132 | yml_string = ymlfile.read()
133 | return yaml.load(yml_string)
134 |
135 | def handle(self, *args, **options):
136 | should_ask = not options['flush']
137 | Flush().handle(interactive=should_ask, database=DEFAULT_DB_ALIAS, **options)
138 | assert hasattr(settings, 'FIXTURE_FACTORIES'), """
139 | You should define FIXTURE_FACTORIES list into the settings file before creating fixtures.
140 | """
141 | factories = settings.FIXTURE_FACTORIES \
142 | if isinstance(settings.FIXTURE_FACTORIES, dict) \
143 | else self.get_from_yml(settings.FIXTURE_FACTORIES)
144 | self.factories_dict = {}
145 | self.factory_by_models = {}
146 | have_images_list = []
147 | for factory_class_route, factory_obj in factories.items():
148 | factory_class = get_class_from_route(factory_class_route)
149 | cls_name = factory_class._meta.model.__name__
150 | fnc = getattr(self, 'create_' + cls_name.lower() + 's', self.create_data)
151 | factory_obj = factory_obj if factory_obj is not None else {}
152 | objects = fnc(factory_class, **factory_obj)
153 | self.factory_by_models[cls_name] = factory_class_route
154 | self.factories_dict[factory_class_route] = {
155 | 'objects': objects,
156 | 'factory': factory_class
157 | }
158 | image_fields = self.get_image_fields(factory_class)
159 | len(image_fields) > 0 and have_images_list.append((objects, image_fields))
160 | [self.download_and_upload_images(*elements_with_images) for elements_with_images in have_images_list]
161 | self.fakes_generation_finished()
162 |
--------------------------------------------------------------------------------
/free_our_knowledge/settings/dev.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for free_our_knowledge project.
3 |
4 | Generated by 'django-admin startproject' using Django 2.1.4.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.1/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/2.1/ref/settings/
11 | """
12 |
13 | import os
14 | import sys
15 |
16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18 | sys.path.insert(0, os.path.abspath(os.path.join(BASE_DIR, '../apps')))
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = 'gd7^%u6zb3%nb%p^_+^4)f1qom2%8l)32#9n#*m$=jd1ndg_zz'
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = True
28 |
29 | ALLOWED_HOSTS = ['*']
30 |
31 |
32 | # Application definition
33 |
34 | INSTALLED_APPS = [
35 | 'django.contrib.admin',
36 | 'django.contrib.auth',
37 | 'django.contrib.contenttypes',
38 | 'django.contrib.sessions',
39 | 'django.contrib.messages',
40 | 'django.contrib.staticfiles',
41 | 'django_summernote',
42 | 'fok',
43 | 'constance.backends.database',
44 | 'constance',
45 | 'cc_cms'
46 | ]
47 |
48 | MIDDLEWARE = [
49 | 'django.middleware.security.SecurityMiddleware',
50 | 'django.contrib.sessions.middleware.SessionMiddleware',
51 | 'django.middleware.common.CommonMiddleware',
52 | 'django.middleware.csrf.CsrfViewMiddleware',
53 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
54 | 'django.contrib.messages.middleware.MessageMiddleware',
55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
56 | ]
57 |
58 | ROOT_URLCONF = 'free_our_knowledge.urls'
59 |
60 | TEMPLATES = [
61 | {
62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
63 | 'DIRS': [],
64 | 'APP_DIRS': True,
65 | 'OPTIONS': {
66 | 'context_processors': [
67 | 'django.template.context_processors.debug',
68 | 'django.template.context_processors.request',
69 | 'django.contrib.auth.context_processors.auth',
70 | 'django.contrib.messages.context_processors.messages',
71 | 'fok.context_processors.add_config'
72 | ],
73 | },
74 | },
75 | ]
76 |
77 | WSGI_APPLICATION = 'free_our_knowledge.wsgi.application'
78 |
79 |
80 | # Database
81 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
82 |
83 | DATABASES = {
84 | 'default': {
85 | 'ENGINE': 'django.db.backends.postgresql',
86 | 'NAME': 'fok',
87 | 'USER': 'postgres',
88 | 'PASSWORD': 'mysecretpassword',
89 | 'HOST': 'docker_db_1', #os.environ.get('DB_HOST', '127.0.0.1')
90 | }
91 | }
92 |
93 |
94 | # Password validation
95 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
96 |
97 | AUTH_PASSWORD_VALIDATORS = [
98 | {
99 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
100 | },
101 | {
102 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
103 | },
104 | {
105 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
106 | },
107 | {
108 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
109 | },
110 | ]
111 |
112 |
113 | # Internationalization
114 | # https://docs.djangoproject.com/en/2.1/topics/i18n/
115 |
116 | LANGUAGE_CODE = 'en-us'
117 |
118 | TIME_ZONE = 'UTC'
119 |
120 | USE_I18N = True
121 |
122 | USE_L10N = True
123 |
124 | USE_TZ = True
125 |
126 | AUTHENTICATION_BACKENDS = [
127 | # 'django.contrib.auth.backends.ModelBackend'
128 | 'fok.backends.OrcidBackend'
129 | ]
130 |
131 | USE_ORCID = False
132 | BASE_ORCID_URL = None
133 | ORCID_ID = None
134 | ORCID_SECRET = None
135 | ORCID_REDIRECT_URL = None
136 | BASE_ORCID_API_URL = None
137 |
138 | # Static files (CSS, JavaScript, Images)
139 | # https://docs.djangoproject.com/en/2.1/howto/static-files/
140 |
141 | STATIC_URL = '/static/'
142 | STATIC_ROOT = 'static'
143 | LOGIN_REDIRECT_URL = '/'
144 |
145 | DEV_SETTINGS_MODULE = 'free_our_knowledge.settings.dev'
146 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
147 | AUTH_USER_MODEL = 'fok.User'
148 | SIGNUP_FORM = 'fok.forms.FokSignUpForm'
149 |
150 | FIXTURE_FACTORIES = 'free_our_knowledge/resources/fixtures.yml'
151 |
152 | CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
153 | CONSTANCE_CONFIG = {
154 | 'OVERRIDE_SUPPORT_METRICS_TEXT': ("", """
155 | If this text value is not empty, when a display metric is gonna be shown will be replaced by this text.
156 | """
157 | ),
158 | 'ANONYMOUS_USERS_FACTOR': (0.25, """
159 | The factor to estimate the impact into the support metric for non registered users.
160 | """),
161 | 'FRONTPAGE_SENTENCE': (
162 | 'People empowering the future of scholarly communication',
163 | 'The sentence that appears over the image into the front page. '
164 | ),
165 | 'FRONTPAGE_VIDEO_URL': (
166 | 'https://s3.wasabisys.com/codi.coop/fok-mvp/FreeOurKnowledge_linked2website.mp4',
167 | 'Video url for the front page.'
168 | ),
169 | 'CAMPAIGNS_TITLE_IN_FRONTPAGE': (
170 | 'Together we can fix academia',
171 | 'Text shown as title on the frontpage for campaigns section.'
172 | ),
173 | 'INTRODUCTION_TITLE_IN_FRONTPAGE': (
174 | 'Scholarly publishing is broken',
175 | 'Text shown as title on the frontpage for the introduction section.'
176 | ),
177 | 'ACTIVATE_GREETINGS': (
178 | True,
179 | 'Activate greetings window.'
180 | ),
181 | 'TITLE_GREETINGS': (
182 | 'Welcome to Free Our Knowledge!',
183 | 'Title for the pop-up greetings window.'
184 | ),
185 | 'TEXT_GREETINGS': (
186 | 'This is Free Our Knowledge, give me your mail NOW:',
187 | 'Text for the pop-up greetings window.'
188 | ),
189 | 'SHOW_SIGNUP_MAILING_ON_GREETINGS': (
190 | True,
191 | 'Check this if you want to show the sign up for the mailing list on the greetings pop-up.'
192 | ),
193 | 'COOKIES_TEXT': (
194 | 'We use cookies to ensure that we give you the best experience on our website.',
195 | 'Add text for the cookies warning.'
196 | ),
197 | 'TWITTER_PROFILE': (
198 | 'https://twitter.com/', ''
199 | ),
200 | 'FACEBOOK_PROFILE': (
201 | 'https://www.facebook.com/', ''
202 | ),
203 | 'INSTAGRAM_PROFILE': (
204 | 'https://www.instagram.com/', ''
205 | ),
206 | 'YOUTUBE_PROFILE': (
207 | 'https://www.youtube.com/', ''
208 | ),
209 | 'GITHUB_PROFILE': (
210 | 'https://www.github.com/', ''
211 | )
212 | }
213 |
214 | SIGNUP_SEND_MAIL = False
215 | SIGNUP_ACTIVE_USER_BY_DEFAULT = True
216 |
217 | FIXTURES_PATH_TO_COVER_IMAGES = 'test-images/logos'
218 |
219 | # Storage Service
220 |
221 | AWS_ACCESS_KEY_ID = ''
222 | AWS_SECRET_ACCESS_KEY = ''
223 | AWS_STORAGE_BUCKET_NAME = 'codi.coop.test'
224 | AWS_S3_CUSTOM_DOMAIN = f's3.wasabisys.com/{AWS_STORAGE_BUCKET_NAME}'
225 | AWS_S3_ENDPOINT_URL = 'https://s3.wasabisys.com'
226 | AWS_DEFAULT_ACL = 'public-read'
227 | # DEFAULT_FILE_STORAGE = 'cc_lib.storages.MediaStorage'
228 | DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
229 | EXTERNAL_MEDIA_PATH = 'fok/media'
230 | MEDIA_FILE_OVERWRITE = True
231 |
--------------------------------------------------------------------------------
/apps/fok/static/styles/scss/components/_buttons.scss:
--------------------------------------------------------------------------------
1 | // shared styles
2 | .btn,
3 | .btn-flat {
4 | border: $button-border;
5 | border-radius: $button-radius;
6 | display: inline-block;
7 | height: $button-height;
8 | line-height: $button-height;
9 | padding: $button-padding;
10 | text-transform: uppercase;
11 | vertical-align: middle;
12 | -webkit-tap-highlight-color: transparent; // Gets rid of tap active state
13 | }
14 |
15 | // Disabled shared style
16 | .btn.disabled,
17 | .btn-floating.disabled,
18 | .btn-large.disabled,
19 | .btn-small.disabled,
20 | .btn-flat.disabled,
21 | .btn:disabled,
22 | .btn-floating:disabled,
23 | .btn-large:disabled,
24 | .btn-small:disabled,
25 | .btn-flat:disabled,
26 | .btn[disabled],
27 | .btn-floating[disabled],
28 | .btn-large[disabled],
29 | .btn-small[disabled],
30 | .btn-flat[disabled] {
31 | pointer-events: none;
32 | background-color: $button-disabled-background !important;
33 | box-shadow: none;
34 | color: $button-disabled-color !important;
35 | cursor: default;
36 | &:hover {
37 | background-color: $button-disabled-background !important;
38 | color: $button-disabled-color !important;
39 | }
40 | }
41 |
42 | // Shared icon styles
43 | .btn,
44 | .btn-floating,
45 | .btn-large,
46 | .btn-small,
47 | .btn-flat {
48 | font-size: $button-font-size;
49 | outline: 0;
50 | i {
51 | font-size: $button-icon-font-size;
52 | line-height: inherit;
53 | }
54 | }
55 |
56 | // Shared focus button style
57 | .btn,
58 | .btn-floating {
59 | &:focus {
60 | background-color: darken($button-raised-background, 10%);
61 | }
62 | }
63 |
64 | // Raised Button
65 | .btn {
66 | text-decoration: none;
67 | color: $button-raised-color;
68 | background-color: $button-raised-background;
69 | text-align: center;
70 | letter-spacing: .5px;
71 | @extend .z-depth-1;
72 | transition: background-color .2s ease-out;
73 | cursor: pointer;
74 | &:hover {
75 | background-color: $button-raised-background-hover;
76 | @extend .z-depth-1-half;
77 | }
78 | }
79 |
80 | // Floating button
81 | .btn-floating {
82 | &:hover {
83 | background-color: $button-floating-background-hover;
84 | @extend .z-depth-1-half;
85 | }
86 | &:before {
87 | border-radius: 0;
88 | }
89 | &.btn-large {
90 | &.halfway-fab {
91 | bottom: -$button-floating-large-size / 2;
92 | }
93 | width: $button-floating-large-size;
94 | height: $button-floating-large-size;
95 | padding: 0;
96 | i {
97 | line-height: $button-floating-large-size;
98 | }
99 | }
100 |
101 | &.btn-small {
102 | &.halfway-fab {
103 | bottom: -$button-floating-small-size / 2;
104 | }
105 | width: $button-floating-small-size;
106 | height: $button-floating-small-size;
107 | i {
108 | line-height: $button-floating-small-size;
109 | }
110 | }
111 |
112 | &.halfway-fab {
113 | &.left {
114 | right: auto;
115 | left: 24px;
116 | }
117 | position: absolute;
118 | right: 24px;
119 | bottom: -$button-floating-size / 2;
120 | }
121 | display: inline-block;
122 | color: $button-floating-color;
123 | position: relative;
124 | overflow: hidden;
125 | z-index: 1;
126 | width: $button-floating-size;
127 | height: $button-floating-size;
128 | line-height: $button-floating-size;
129 | padding: 0;
130 | background-color: $button-floating-background;
131 | border-radius: $button-floating-radius;
132 | @extend .z-depth-1;
133 | transition: background-color .3s;
134 | cursor: pointer;
135 | vertical-align: middle;
136 | i {
137 | width: inherit;
138 | display: inline-block;
139 | text-align: center;
140 | color: $button-floating-color;
141 | font-size: $button-large-icon-font-size;
142 | line-height: $button-floating-size;
143 | }
144 | }
145 |
146 | // button fix
147 | button.btn-floating {
148 | border: $button-border;
149 | }
150 |
151 | // Fixed Action Button
152 | .fixed-action-btn {
153 | &.active {
154 | ul {
155 | visibility: visible;
156 | }
157 | }
158 |
159 | // Directions
160 | &.direction-left,
161 | &.direction-right {
162 | padding: 0 0 0 15px;
163 | ul {
164 | text-align: right;
165 | right: 64px;
166 | top: 50%;
167 | transform: translateY(-50%);
168 | height: 100%;
169 | left: auto;
170 | /*width 100% only goes to width of button container */
171 | width: 500px;
172 | li {
173 | display: inline-block;
174 | margin: 7.5px 15px 0 0;
175 | }
176 | }
177 | }
178 | &.direction-right {
179 | padding: 0 15px 0 0;
180 | ul {
181 | text-align: left;
182 | direction: rtl;
183 | left: 64px;
184 | right: auto;
185 | li {
186 | margin: 7.5px 0 0 15px;
187 | }
188 | }
189 | }
190 | &.direction-bottom {
191 | padding: 0 0 15px 0;
192 | ul {
193 | top: 64px;
194 | bottom: auto;
195 | display: flex;
196 | flex-direction: column-reverse;
197 | li {
198 | margin: 15px 0 0 0;
199 | }
200 | }
201 | }
202 | &.toolbar {
203 | &.active {
204 | &>a i {
205 | opacity: 0;
206 | }
207 | }
208 | padding: 0;
209 | height: $button-floating-large-size;
210 | ul {
211 | display: flex;
212 | top: 0;
213 | bottom: 0;
214 | z-index: 1;
215 | li {
216 | flex: 1;
217 | display: inline-block;
218 | margin: 0;
219 | height: 100%;
220 | transition: none;
221 | a {
222 | display: block;
223 | overflow: hidden;
224 | position: relative;
225 | width: 100%;
226 | height: 100%;
227 | background-color: transparent;
228 | box-shadow: none;
229 | color: #fff;
230 | line-height: $button-floating-large-size;
231 | z-index: 1;
232 | i {
233 | line-height: inherit;
234 | }
235 | }
236 | }
237 | }
238 | }
239 | position: fixed;
240 | right: 23px;
241 | bottom: 23px;
242 | padding-top: 15px;
243 | margin-bottom: 0;
244 | z-index: 997;
245 | ul {
246 | left: 0;
247 | right: 0;
248 | text-align: center;
249 | position: absolute;
250 | bottom: 64px;
251 | margin: 0;
252 | visibility: hidden;
253 | li {
254 | margin-bottom: 15px;
255 | }
256 | a.btn-floating {
257 | opacity: 0;
258 | }
259 | }
260 | .fab-backdrop {
261 | position: absolute;
262 | top: 0;
263 | left: 0;
264 | z-index: -1;
265 | width: $button-floating-size;
266 | height: $button-floating-size;
267 | background-color: $button-floating-background;
268 | border-radius: $button-floating-radius;
269 | transform: scale(0);
270 | }
271 | }
272 |
273 | // Flat button
274 | .btn-flat {
275 | box-shadow: none;
276 | background-color: transparent;
277 | color: $button-flat-color;
278 | cursor: pointer;
279 | transition: background-color .2s;
280 | &:focus,
281 | &:hover {
282 | box-shadow: none;
283 | }
284 | &:focus {
285 | background-color: rgba(0, 0, 0, .1);
286 | }
287 | &.disabled,
288 | &.btn-flat[disabled] {
289 | background-color: transparent !important;
290 | color: $button-flat-disabled-color !important;
291 | cursor: default;
292 | }
293 | }
294 |
295 | // Large button
296 | .btn-large {
297 | @extend .btn;
298 | height: $button-large-height;
299 | line-height: $button-large-height;
300 | font-size: $button-large-font-size;
301 | padding: 0 28px;
302 |
303 | i {
304 | font-size: $button-large-icon-font-size;
305 | }
306 | }
307 |
308 | // Small button
309 | .btn-small {
310 | @extend .btn;
311 | height: $button-small-height;
312 | line-height: $button-small-height;
313 | font-size: $button-small-font-size;
314 | i {
315 | font-size: $button-small-icon-font-size;
316 | }
317 | }
318 |
319 | // Block button
320 | .btn-block {
321 | display: block;
322 | }
323 |
--------------------------------------------------------------------------------