Skip to content

Commit 82716a0

Browse files
committed
Enhance the community website homepage
The enhancement includes addition of materialize css, JQuery, responsiveness, and easy-navigation of website features. The easy-navigatibility is achieved by adding a navbar with display of meta -review and gamification leaderboard on homepage. Apart from this, the activity graph url is omitted from website by displaying the graph itslef on the homepage on large devices. Closes #255
1 parent 3a9f06c commit 82716a0

28 files changed

+983
-234
lines changed

.ci/build.sh

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ python manage.py fetch_deployed_data _site $ISSUES_JSON \
2828

2929
python manage.py migrate
3030
python manage.py import_contributors_data
31+
python manage.py create_org_cluster_map_and_activity_graph org_map
3132
python manage.py import_issues_data
3233
python manage.py import_merge_requests_data
3334
python manage.py create_config_data

.coafile

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[all]
22
files = **.py, **.js, **.sh
3-
ignore = .git/**, **/__pycache__/**, gci/client.py, */migrations/**, private/*, openhub/**
3+
ignore = .git/**, **/__pycache__/**, gci/client.py, */migrations/**, private/*, openhub/**, **/leaflet_dist/**
44
max_line_length = 80
55
use_spaces = True
66
preferred_quotation = '
@@ -42,6 +42,7 @@ files = static/**/*.js
4242
bears = JSHintBear
4343
allow_unused_variables = True
4444
javascript_strictness = False
45+
environment_jquery = True
4546

4647
[all.yml]
4748
bears = YAMLLintBear

.moban.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ packages:
1212
- log
1313
- meta_review
1414
- model
15-
- twitter
1615
- unassigned_issues
1716

1817
dependencies:
18+
- getorg~=0.3.1
1919
- git+https://gitlab.com/coala/coala-utils.git
2020
- git-url-parse
2121
- django>2.1,<2.2

.nocover.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ nocover_file_globs:
1212
- meta_review/handler.py
1313
- model/*.py
1414
- openhub/*.py
15-
- twitter/*.py
1615
# Optional coverage. Once off scripts.
1716
- inactive_issues/inactive_issues_scraper.py
1817
- unassigned_issues/unassigned_issues_scraper.py

activity/scraper.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def get_data(self):
136136
return self.data
137137

138138

139-
def activity_json(request):
139+
def activity_json(filename):
140140

141141
org_name = get_org_name()
142142

@@ -152,4 +152,5 @@ def activity_json(request):
152152
real_data = Scraper(parsed_json['issues'], datetime.datetime.today())
153153
real_data = real_data.get_data()
154154

155-
return HttpResponse(json.dumps(real_data))
155+
with open(filename, 'w+') as f:
156+
json.dump(real_data, f, indent=4)

community/urls.py

-21
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
from django_distill import distill_url
66
from django.conf.urls.static import static
77
from django.conf import settings
8-
from django.views.generic import TemplateView
98

109
from community.views import HomePageView, info
1110
from gci.views import index as gci_index
1211
from gci.feeds import LatestTasksFeed as gci_tasks_rss
13-
from activity.scraper import activity_json
14-
from twitter.view_twitter import index as twitter_index
1512
from log.view_log import index as log_index
1613
from data.views import index as contributors_index
1714
from gamification.views import index as gamification_index
@@ -87,18 +84,6 @@ def get_organization():
8784
distill_func=get_index,
8885
distill_file='info.txt',
8986
),
90-
distill_url(
91-
r'static/activity-data.json', activity_json,
92-
name='activity_json',
93-
distill_func=get_index,
94-
distill_file='static/activity-data.json',
95-
),
96-
distill_url(
97-
r'activity/', TemplateView.as_view(template_name='activity.html'),
98-
name='activity',
99-
distill_func=get_index,
100-
distill_file='activity/index.html',
101-
),
10287
distill_url(
10388
r'gci/tasks/rss.xml', gci_tasks_rss(),
10489
name='gci-tasks-rss',
@@ -111,12 +96,6 @@ def get_organization():
11196
distill_func=get_index,
11297
distill_file='gci/index.html',
11398
),
114-
distill_url(
115-
r'twitter/', twitter_index,
116-
name='twitter',
117-
distill_func=get_index,
118-
distill_file='twitter/index.html',
119-
),
12099
distill_url(
121100
r'log/', log_index,
122101
name='log',

community/views.py

+99-9
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,118 @@
1-
from django.http import HttpResponse
2-
from django.views.generic.base import TemplateView
1+
import logging
2+
3+
import requests
34

45
from trav import Travis
56

7+
from django.http import HttpResponse
8+
from django.views.generic.base import TemplateView
9+
610
from .git import (
711
get_deploy_url,
812
get_org_name,
913
get_owner,
1014
get_upstream_deploy_url,
15+
get_remote_url
1116
)
17+
from data.models import Team
18+
from gamification.models import Participant as GamificationParticipant
19+
from meta_review.models import Participant as MetaReviewer
20+
21+
22+
def initialize_org_context_details():
23+
org_name = get_org_name()
24+
org_details = {
25+
'name': org_name,
26+
'blog_url': f'https://blog.{org_name}.io/',
27+
'twitter_url': f'https://twitter.com/{org_name}_io/',
28+
'facebook_url': f'https://www.facebook.com/{org_name}Analyzer',
29+
'repo_url': get_remote_url().href,
30+
'docs': f'https://{org_name}.io/docs',
31+
'newcomer_docs': f'https://{org_name}.io/newcomer',
32+
'coc': f'https://{org_name}.io/coc',
33+
'logo_url': (f'https://api.{org_name}.io/en/latest/_static/images/'
34+
f'{org_name}_logo.svg'),
35+
'gitter_chat': f'https://gitter.im/{org_name}/{org_name}/',
36+
'github_core_repo': f'https://github.com/{org_name}/{org_name}/',
37+
'licence_type': 'GNU AGPL v3.0'
38+
}
39+
return org_details
40+
41+
42+
def get_header_and_footer(context):
43+
context['isTravis'] = Travis.TRAVIS
44+
context['travisLink'] = Travis.TRAVIS_BUILD_WEB_URL
45+
context['org'] = initialize_org_context_details()
46+
print('Running on Travis: {}, build link: {}'.format(context['isTravis'],
47+
context['travisLink']
48+
))
49+
return context
1250

1351

1452
class HomePageView(TemplateView):
1553
template_name = 'index.html'
1654

17-
def get_context_data(self, **kwargs):
18-
context = super().get_context_data(**kwargs)
19-
context['isTravis'] = Travis.TRAVIS
20-
context['travisLink'] = Travis.TRAVIS_BUILD_WEB_URL
55+
def get_team_details(self, org_name):
56+
teams = [
57+
f'{org_name} newcomers',
58+
f'{org_name} developers',
59+
f'{org_name} admins'
60+
]
61+
team_details = {}
62+
for team_name in teams:
63+
team = Team.objects.get(name=team_name)
64+
contributors_count = team.contributors.count()
65+
team_details[
66+
team_name.replace(org_name, '').strip().capitalize()
67+
] = contributors_count
68+
return team_details
69+
70+
def get_quote_of_the_day(self):
71+
72+
try:
73+
qod = requests.get('http://quotes.rest/qod?category=inspire')
74+
qod.raise_for_status()
75+
except requests.HTTPError as err:
76+
error_info = f'HTTPError while fetching Quote of the day! {err}'
77+
logging.error(error_info)
78+
return
2179

22-
print('Running on Travis: {}, build link: {}'.format(
23-
context['isTravis'],
24-
context['travisLink']))
80+
qod_data = qod.json()
81+
return {
82+
'quote': qod_data['contents']['quotes'][0]['quote'],
83+
'author': qod_data['contents']['quotes'][0]['author'],
84+
}
2585

86+
def get_top_meta_review_users(self, count):
87+
participants = MetaReviewer.objects.all()[:count]
88+
return participants
89+
90+
def get_top_gamification_users(self, count):
91+
return enumerate(GamificationParticipant.objects.all()[:count])
92+
93+
def get_context_data(self, **kwargs):
94+
context = super().get_context_data(**kwargs)
95+
context = get_header_and_footer(context)
96+
org_name = context['org']['name']
97+
context['org']['team_details'] = dict(self.get_team_details(org_name))
98+
about_org = (f'{org_name} (always spelled with a lowercase c!) is one'
99+
' of the welcoming open-source organizations for'
100+
f' newcomers. {org_name} stands for “COde AnaLysis'
101+
' Application” as it works well with animals and thus is'
102+
' well visualizable which makes it easy to memorize.'
103+
f' {org_name} provides a unified interface for linting'
104+
' and fixing the code with a single configuration file,'
105+
' regardless of the programming languages used. You can'
106+
f' use {org_name} from within your favorite editor,'
107+
' integrate it with your CI and, get the results as JSON'
108+
', or customize it to your needs with its flexible'
109+
' configuration syntax.')
110+
context['org']['about'] = about_org
111+
context['quote_details'] = self.get_quote_of_the_day()
112+
context['top_meta_review_users'] = self.get_top_meta_review_users(
113+
count=5)
114+
context['top_gamification_users'] = self.get_top_gamification_users(
115+
count=5)
26116
return context
27117

28118

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from django.core.management.base import BaseCommand
2+
3+
from data.org_cluster_map_handler import handle as org_cluster_map_handler
4+
from activity.scraper import activity_json
5+
6+
7+
class Command(BaseCommand):
8+
help = 'Create a cluster map using contributors geolocation'
9+
10+
def add_arguments(self, parser):
11+
parser.add_argument('output_dir', nargs='?', type=str)
12+
13+
def handle(self, *args, **options):
14+
output_dir = options.get('output_dir')
15+
if not output_dir:
16+
org_cluster_map_handler()
17+
else:
18+
org_cluster_map_handler(output_dir)
19+
# Fetch & Store data for activity graph to be displayed on home-page
20+
activity_json('static/activity-data.js')
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.1.7 on 2019-08-01 17:52
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('data', '0005_auto_20190801_1442'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='contributor',
15+
name='teams',
16+
field=models.ManyToManyField(related_name='contributors', to='data.Team'),
17+
),
18+
]

data/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Contributor(models.Model):
1919
reviews = models.IntegerField(default=None, null=True)
2020
issues_opened = models.IntegerField(default=None, null=True)
2121
location = models.TextField(default=None, null=True)
22-
teams = models.ManyToManyField(Team)
22+
teams = models.ManyToManyField(Team, related_name='contributors')
2323

2424
def __str__(self):
2525
return self.login

data/org_cluster_map_handler.py

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import os
2+
import json
3+
4+
import logging
5+
6+
import getorg
7+
8+
from data.models import Contributor
9+
10+
11+
def handle(output_dir='cluster_map'):
12+
"""
13+
Creates a organization cluster map using the contributors location
14+
stored in the database
15+
:param output_dir: Directory where all the required CSS and JS files
16+
are copied by 'getorg' package
17+
"""
18+
logger = logging.getLogger(__name__)
19+
logger.info("'cluster_map/' is the default directory for storing"
20+
" organization map related files. If arg 'output_dir'"
21+
' not provided it will be used as a default directory by'
22+
" 'getorg' package.")
23+
24+
# For creating the organization map, the 'getorg' uses a 'Nominatim' named
25+
# package which geocodes the contributor location and then uses that class
26+
# to create the map. Since, we're not dealing with that function which use
27+
# that 'Nominatim' package because we're fetching a JSON data and storing
28+
# it in our db. Therefore, defining our own simple class that can aid us
29+
# to create a cluster map.
30+
class Location:
31+
32+
def __init__(self, longitude, latitude):
33+
self.longitude = longitude
34+
self.latitude = latitude
35+
36+
org_location_dict = {}
37+
38+
for contrib in Contributor.objects.filter(location__isnull=False):
39+
user_location = json.loads(contrib.location)
40+
location = Location(user_location['longitude'],
41+
user_location['latitude'])
42+
org_location_dict[contrib.login] = location
43+
logger.debug(f'{contrib.login} location {user_location} added on map')
44+
getorg.orgmap.output_html_cluster_map(org_location_dict,
45+
folder_name=output_dir)
46+
47+
move_and_make_changes_in_files(output_dir)
48+
49+
50+
def move_and_make_changes_in_files(output_dir):
51+
"""
52+
Move static files from 'output_dir' to django static folder which
53+
is being required by the map.html which is being auto-generated
54+
by getorg.
55+
:param output_dir: Directory from where the files have to be moved
56+
"""
57+
58+
move_leaflet_dist_folder(output_dir)
59+
60+
os.rename(
61+
src=get_file_path(os.getcwd(), output_dir, 'org-locations.js'),
62+
dst=get_file_path(os.getcwd(), 'static', 'org-locations.js')
63+
)
64+
65+
os.remove(get_file_path(os.getcwd(), output_dir, 'map.html'))
66+
67+
68+
def move_leaflet_dist_folder(output_dir):
69+
source_path = get_file_path(os.getcwd(), output_dir, 'leaflet_dist')
70+
destination_path = get_file_path(os.getcwd(), 'static', 'leaflet_dist')
71+
72+
# Remove existing leaflet_dir if exists
73+
for root, dirs, files in os.walk(destination_path):
74+
for file in files:
75+
os.remove(os.path.join(destination_path, file))
76+
os.rmdir(root)
77+
78+
os.renames(source_path, destination_path)
79+
80+
81+
def get_file_path(*args):
82+
return '/'.join(args)
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from django.test import TestCase
2+
3+
from data.models import Contributor
4+
from data.org_cluster_map_handler import handle as org_cluster_map_handler
5+
6+
7+
class CreateOrgClusterMapAndActivityGraphTest(TestCase):
8+
9+
@classmethod
10+
def setUpTestData(cls):
11+
Contributor.objects.create(login='test',
12+
name='Test User',
13+
location='{"latitude": 12.9,'
14+
'"longitude": 77.8}')
15+
Contributor.objects.create(login='testuser',
16+
name='Test User 2')
17+
18+
def test_with_output_dir(self):
19+
org_cluster_map_handler()
20+
21+
def test_without_output_dir(self):
22+
org_cluster_map_handler(output_dir='org_map')

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
getorg~=0.3.1
12
git+https://gitlab.com/coala/coala-utils.git
23
git-url-parse
34
django>2.1,<2.2

0 commit comments

Comments
 (0)