Skip to content

Commit ec61b5e

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 coala#255
1 parent 610f8d1 commit ec61b5e

25 files changed

+961
-190
lines changed

.ci/build.sh

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

2828
python manage.py migrate
2929
python manage.py import_contributors_data
30+
python manage.py create_org_cluster_map_and_activity_graph org_map
3031
python manage.py import_issues_data
3132
python manage.py import_merge_requests_data
3233
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
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ packages:
1616
- unassigned_issues
1717

1818
dependencies:
19+
- getorg~=0.3.1
1920
- git+https://gitlab.com/coala/coala-utils.git
2021
- git-url-parse
2122
- django>2.1,<2.2

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

-14
Original file line numberDiff line numberDiff line change
@@ -5,12 +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
1412
from twitter.view_twitter import index as twitter_index
1513
from log.view_log import index as log_index
1614
from data.views import index as contributors_index
@@ -87,18 +85,6 @@ def get_organization():
8785
distill_func=get_index,
8886
distill_file='info.txt',
8987
),
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-
),
10288
distill_url(
10389
r'gci/tasks/rss.xml', gci_tasks_rss(),
10490
name='gci-tasks-rss',

community/views.py

+90-8
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,110 @@
11
from django.http import HttpResponse
22
from django.views.generic.base import TemplateView
3-
3+
import requests
4+
import logging
45
from trav import Travis
56

67
from .git import (
78
get_deploy_url,
89
get_org_name,
910
get_owner,
1011
get_upstream_deploy_url,
12+
get_remote_url
1113
)
14+
from data.models import Team
15+
from gamification.models import Participant as GamificationParticipant
16+
from meta_review.models import Participant as MetaReviewer
17+
18+
19+
def initialize_org_context_details():
20+
org_name = get_org_name()
21+
org_details = {
22+
'name': org_name,
23+
'blog_url': f'https://blog.{org_name}.io/',
24+
'twitter_url': f'https://twitter.com/{org_name}_io/',
25+
'facebook_url': f'https://www.facebook.com/{org_name}Analyzer',
26+
'repo_url': get_remote_url().href,
27+
'docs': f'https://{org_name}.io/docs',
28+
'newcomer_docs': f'https://{org_name}.io/newcomer',
29+
'coc': f'https://{org_name}.io/coc',
30+
'logo_url': (f'https://api.{org_name}.io/en/latest/_static/images/'
31+
f'{org_name}_logo.svg'),
32+
'gitter_chat': f'https://gitter.im/{org_name}/{org_name}/',
33+
'github_core_repo': f'https://github.com/{org_name}/{org_name}/',
34+
'licence_type': 'GNU AGPL v3.0'
35+
}
36+
return org_details
37+
38+
39+
def get_header_and_footer(context):
40+
context['isTravis'] = Travis.TRAVIS
41+
context['travisLink'] = Travis.TRAVIS_BUILD_WEB_URL
42+
context['org'] = initialize_org_context_details()
43+
print('Running on Travis: {}, build link: {}'.format(context['isTravis'],
44+
context['travisLink']
45+
))
46+
return context
1247

1348

1449
class HomePageView(TemplateView):
1550
template_name = 'index.html'
1651

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
52+
def get_team_details(self, org_name):
53+
teams = [
54+
'{} newcomers'.format(org_name),
55+
'{} developers'.format(org_name),
56+
'{} admins'.format(org_name)
57+
]
58+
team_details = {}
59+
for team_name in teams:
60+
team = Team.objects.get(name=team_name)
61+
team_details[team] = team.contributors.count()
62+
return team_details
63+
64+
def get_quote_of_the_day(self):
65+
qod = requests.get('http://quotes.rest/qod?category=inspire')
66+
qod_data = qod.json()
67+
try:
68+
return {
69+
'quote': qod_data['contents']['quotes'][0]['quote'],
70+
'author': qod_data['contents']['quotes'][0]['author'],
71+
}
72+
except requests.HTTPError as err:
73+
error_info = f'HTTPError while fetching Quote of the day! {err}'
74+
print(error_info)
75+
logging.error(error_info)
76+
return None
2177

22-
print('Running on Travis: {}, build link: {}'.format(
23-
context['isTravis'],
24-
context['travisLink']))
78+
def get_top_meta_review_users(self, count):
79+
participants = MetaReviewer.objects.all()[:count]
80+
return participants
2581

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

28110

data/contrib_data.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ def import_data(contributor):
3232
try:
3333
contributor['issues_opened'] = contributor.pop('issues')
3434
contributor['num_commits'] = contributor.pop('contributions')
35-
contributor_location = contributor.get('location')
36-
if contributor_location:
37-
contributor['location'] = contributor_location
35+
contributor['location'] = contributor.get('location')
3836
contributor.pop('teams')
3937
c, create = Contributor.objects.get_or_create(
4038
**contributor
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-06-15 13:31
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('data', '0005_contributor_location'),
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
@@ -16,7 +16,7 @@ class Contributor(models.Model):
1616
reviews = models.IntegerField(default=None, null=True)
1717
issues_opened = models.IntegerField(default=None, null=True)
1818
location = models.TextField(default=None, null=True)
19-
teams = models.ManyToManyField(Team)
19+
teams = models.ManyToManyField(Team, related_name='contributors')
2020

2121
def __str__(self):
2222
return self.login

data/org_cluster_map_handler.py

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import json
2+
import logging
3+
import os
4+
import getorg
5+
6+
from data.models import Contributor
7+
8+
9+
def handle(output_dir='cluster_map'):
10+
"""
11+
Creates a organization cluster map using the contributors location
12+
stored in the database
13+
:param output_dir: Directory where all the required CSS and JS files
14+
are copied by 'getorg' package
15+
:return: None
16+
"""
17+
logger = logging.getLogger(__name__)
18+
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.all():
39+
if not contrib.login.startswith('testuser') and contrib.location:
40+
user_location = json.loads(contrib.location)
41+
location = Location(user_location['longitude'],
42+
user_location['latitude'])
43+
org_location_dict[contrib.login] = location
44+
logger.debug('{} location {} added on map'.format(
45+
contrib.login, user_location))
46+
getorg.orgmap.output_html_cluster_map(org_location_dict,
47+
folder_name=output_dir)
48+
49+
move_and_make_changes_in_files(output_dir)
50+
51+
52+
def move_and_make_changes_in_files(output_dir):
53+
"""
54+
# Move static files from 'output_dir' to django static folder and
55+
# the 'map.html' file to django templates directory to get it displayed
56+
# on the homepage with the needed django syntax and CSS in the html file.
57+
:param output_dir: Directory from where the files have to be moved
58+
:return: None
59+
"""
60+
61+
# Move leaflet_dist folder to static folder
62+
leaflet_source_path = '{}/{}/leaflet_dist/'.format(os.getcwd(),
63+
output_dir)
64+
leaflet_destination_path = '{}/{}/leaflet_dist/'.format(os.getcwd(),
65+
'static')
66+
67+
# Remove existing leaflet_dir if exists
68+
for root, dirs, files in os.walk(leaflet_destination_path):
69+
for file in files:
70+
os.remove(os.path.join(leaflet_destination_path, file))
71+
os.rmdir(root)
72+
73+
os.renames(leaflet_source_path, leaflet_destination_path)
74+
75+
# Move org_locations.js to static folder
76+
locations_source_path = '{}/{}/org-locations.js'.format(os.getcwd(),
77+
output_dir)
78+
locations_destination_path = '{}/{}/org-locations.js'.format(
79+
os.getcwd(), 'static')
80+
os.rename(locations_source_path, locations_destination_path)
81+
82+
# Make changes in map.html to support django syntax with needed CSS
83+
with open('{}/map.html'.format(output_dir)) as f:
84+
django_supported_htmls = []
85+
lines = f.readlines()
86+
for index in range(len(lines)):
87+
line = lines[index].strip()
88+
if line.__contains__('<html>'):
89+
django_supported_htmls.append('{% load staticfiles %}\n')
90+
django_supported_htmls.append(line + '\n')
91+
elif line.__contains__('</head>'):
92+
adjust_prop = '''
93+
<style>
94+
#map {
95+
width: 60%;
96+
height: 300px;
97+
margin: auto;
98+
box-shadow: 0px 0px 25px 2px;
99+
}
100+
@media only screen and (max-width:750px){
101+
#map {
102+
width: 90%
103+
}
104+
}
105+
</style>\n
106+
'''
107+
meta_charset = '<meta charset="utf-8">'
108+
adjust_prop = adjust_prop.strip().replace(' ', '')
109+
django_supported_htmls.insert(6, meta_charset + '\n')
110+
django_supported_htmls.append(adjust_prop + '\n')
111+
django_supported_htmls.append(line + '\n')
112+
elif line.__contains__('https://'):
113+
line = line.replace('https:', '').replace(' />', '>')
114+
django_supported_htmls.append(line.strip() + '\n')
115+
elif line.__contains__('<link '):
116+
line = line.replace('href="', 'href="{% static \'')
117+
line = line.replace('.css', '.css\' %}').replace(' />', '>')
118+
django_supported_htmls.append(line + '\n')
119+
elif line.__contains__('<script '):
120+
line = line.replace('src="', 'src="{% static \'')
121+
line = line.replace('.js', '.js\' %}')
122+
if line.__contains__(' type="text/javascript"'):
123+
line = line.replace(' type="text/javascript"', '')
124+
django_supported_htmls.append(line + '\n')
125+
elif line.__contains__('Mouse over') or len(line) == 0:
126+
pass
127+
else:
128+
django_supported_htmls.append(line + '\n')
129+
130+
# Remove and Add 'map.html' to django templates directory
131+
html_map_path = '{}/{}/map.html'
132+
html_map_source_path = html_map_path.format(os.getcwd(),
133+
output_dir)
134+
html_map_destination_path = html_map_path.format(os.getcwd(),
135+
'templates')
136+
os.remove(html_map_source_path)
137+
with open(html_map_destination_path, 'w+') as f:
138+
f.writelines(django_supported_htmls)

0 commit comments

Comments
 (0)