diff --git a/gsoc/admin.py b/gsoc/admin.py index 21005a36..770b6a65 100644 --- a/gsoc/admin.py +++ b/gsoc/admin.py @@ -1,6 +1,6 @@ from .models import (UserProfile, RegLink, UserDetails, Scheduler, PageNotification, AddUserLog, BlogPostDueDate, Builder, Timeline, ArticleReview, Event, SubOrgDetails, - GsocEndDate, Comment, SendEmail) + GsocEndDate, Comment, SendEmail, BlogPostHistory) from .forms import (UserProfileForm, UserDetailsForm, RegLinkForm, BlogPostDueDateForm, EventForm, GsocEndDateForm) @@ -368,6 +368,7 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs): class RegLinkInline(admin.TabularInline): model = RegLink form = RegLinkForm + extra = 1 class AddUserLogAdmin(admin.ModelAdmin): @@ -544,3 +545,18 @@ def has_change_permission(self, request, obj=None): admin.site.register(SendEmail, SendEmailAdmin) + + +class BlogPostHistoryAdmin(admin.ModelAdmin): + list_display = ('article', 'timestamp') + list_filter = ('article', ) + fields = ('article', 'content_safe', 'timestamp') + + def has_change_permission(self, request, obj=None): + return False + + def content_safe(self, obj): + return mark_safe(obj.content) + + +admin.site.register(BlogPostHistory, BlogPostHistoryAdmin) diff --git a/gsoc/cms_toolbars.py b/gsoc/cms_toolbars.py index 2badc6bb..b09a0c0f 100644 --- a/gsoc/cms_toolbars.py +++ b/gsoc/cms_toolbars.py @@ -53,17 +53,34 @@ def add_admin_menu(self): if user and user.is_superuser: self._admin_menu.add_sideframe_item(_('Schedulers'), url=admin_reverse('gsoc_scheduler_changelist')) - self._admin_menu.add_break(ADMINISTRATION_BREAK) - - # cms users settings - self._admin_menu.add_sideframe_item(_('User settings'), url=admin_reverse('cms_usersettings_change')) - self._admin_menu.add_break(USER_SETTINGS_BREAK) - if user and user.is_superuser: + self._admin_menu.add_sideframe_item(_('Builders'), + url=admin_reverse('gsoc_builder_changelist')) + self._admin_menu.add_sideframe_item(_('Review Article'), + url=admin_reverse('gsoc_articlereview_changelist')) + self._admin_menu.add_sideframe_item(_('Timeline'), + url=admin_reverse('gsoc_timeline_changelist')) + self._admin_menu.add_sideframe_item(_('Send Email'), + url=admin_reverse('gsoc_sendemail_add')) + self._admin_menu.add_sideframe_item(_('Suborg Applications'), + url=admin_reverse('gsoc_suborgdetails_changelist')) self._admin_menu.add_modal_item( name='Add Users', url=admin_reverse('gsoc_adduserlog_add'), on_close=None, ) + + if user: + self._admin_menu.add_link_item(_('New Suborg Application'), + reverse('suborg:register_suborg')) + self._admin_menu.add_link_item(_('Manage Suborg Application'), + reverse('suborg:application_list')) + + self._admin_menu.add_break(ADMINISTRATION_BREAK) + + # cms users settings + self._admin_menu.add_sideframe_item(_('User settings'), + url=admin_reverse('cms_usersettings_change')) + self._admin_menu.add_break(USER_SETTINGS_BREAK) # clipboard if self.toolbar.edit_mode_active: # True if the clipboard exists and there's plugins in it. @@ -226,5 +243,10 @@ def populate(self): except ArticleReview.DoesNotExist: pass + if user.is_superuser: + url = (f"{admin_reverse('gsoc_blogposthistory_changelist')}" + f"?article__id__exact={article.id}") + self.toolbar.add_sideframe_item(_('View History'), url=url) + NewsBlogToolbar.populate = populate diff --git a/gsoc/common/utils/memcached_stats.py b/gsoc/common/utils/memcached_stats.py new file mode 100644 index 00000000..9b7d267b --- /dev/null +++ b/gsoc/common/utils/memcached_stats.py @@ -0,0 +1,50 @@ +import re +import telnetlib +import sys + + +class MemcachedStats: + + _client = None + _key_regex = re.compile(r'ITEM (.*) \[(.*); (.*)\]') + _slab_regex = re.compile(r'STAT items:(.*):number') + _stat_regex = re.compile(r"STAT (.*) (.*)\r") + + def __init__(self, host='localhost', port='11211', timeout=None): + self._host = host + self._port = port + self._timeout = timeout + + @property + def client(self): + if self._client is None: + self._client = telnetlib.Telnet(self._host, self._port, + self._timeout) + return self._client + + def command(self, cmd): + ' Write a command to telnet and return the response ' + self.client.write(("%s\n" % cmd).encode('ascii')) + return self.client.read_until(b'END').decode('ascii') + + def key_details(self, sort=True, limit=100): + ' Return a list of tuples containing keys and details ' + cmd = 'stats cachedump %s %s' + keys = [key for id in self.slab_ids() + for key in self._key_regex.findall(self.command(cmd % (id, limit)))] + if sort: + return sorted(keys) + else: + return keys + + def keys(self, sort=True, limit=100): + ' Return a list of keys in use ' + return [key[0] for key in self.key_details(sort=sort, limit=limit)] + + def slab_ids(self): + ' Return a list of slab ids in use ' + return self._slab_regex.findall(self.command('stats items')) + + def stats(self): + ' Return a dict containing memcached stats ' + return dict(self._stat_regex.findall(self.command('stats'))) diff --git a/gsoc/common/utils/tools.py b/gsoc/common/utils/tools.py index 5e91b780..0eedd6eb 100644 --- a/gsoc/common/utils/tools.py +++ b/gsoc/common/utils/tools.py @@ -85,23 +85,30 @@ def is_year(file_name): return False -def get_files(repo, except_files=['CNAME', 'LICENSE.md', 'README.md']): +def get_files(repo, except_files=['CNAME', 'LICENSE.md', 'README.md', 'favicon.ico', 'robots.txt']): contents = repo.get_contents('') files = [] while contents: file_content = contents.pop(0) if not (file_content.path in except_files or is_year(file_content.path)): - if file_content.type == "dir": + if file_content.type == 'dir': contents.extend(repo.get_contents(file_content.path)) else: files.append(file_content) return files +def update_robots_file(repo, current_year): + c = repo.get_contents('robots.txt') + new_content = c.decoded_content.strip() + f'\nDisallow: /{current_year}/\n'.encode() + repo.update_file(c.path, 'Update robots.txt', new_content, c.sha) + + def archive_current_gsoc_files(current_year): g = Github(settings.GITHUB_ACCESS_TOKEN) repo = g.get_repo(settings.STATIC_SITE_REPO) files = get_files(repo) + update_robots_file(repo, current_year) for file in files: try: repo.create_file(f'{current_year}/{file.path}', diff --git a/gsoc/migrations/0049_blogposthistory.py b/gsoc/migrations/0049_blogposthistory.py new file mode 100644 index 00000000..b10bacee --- /dev/null +++ b/gsoc/migrations/0049_blogposthistory.py @@ -0,0 +1,24 @@ +# Generated by Django 2.1.10 on 2019-07-28 09:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('aldryn_newsblog', '0016_auto_20180329_1417'), + ('gsoc', '0048_reglink_send_notifications'), + ] + + operations = [ + migrations.CreateModel( + name='BlogPostHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('content', models.TextField(blank=True, null=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='aldryn_newsblog.Article')), + ], + ), + ] diff --git a/gsoc/models.py b/gsoc/models.py index ff4b198c..9c5cd169 100644 --- a/gsoc/models.py +++ b/gsoc/models.py @@ -48,6 +48,15 @@ def gen_uuid_str(): NewsBlogConfig.__str__ = lambda self: self.app_title +def current_year_profile(self): + gsoc_year = GsocYear.objects.first() + profile = self.userprofile_set.filter(gsoc_year=gsoc_year, role__in=[1, 2, 3]) + return profile.first() if len(profile) > 0 else None + + +auth.models.User.add_to_class('current_year_profile', current_year_profile) + + def has_proposal(self): try: proposal = self.student_profile().accepted_proposal_pdf @@ -112,34 +121,6 @@ def get_root_comments(self): Article.add_to_class('get_root_comments', get_root_comments) -def is_unclean(self): - unclean_texts = ( - '
',
-        '
', - '<', - '>', - ) - for _ in unclean_texts: - if _ in self.lead_in: - return True - return False - - -Article.add_to_class('is_unclean', is_unclean) - - -def clean_article_html(self): - self.lead_in = re.sub(r'
', '', self.lead_in)
-    self.lead_in = re.sub(r'<\/pre>', '', self.lead_in)
-    self.lead_in = re.sub(r'<', '<', self.lead_in)
-    self.lead_in = re.sub(r'>', '>', self.lead_in)
-    self.lead_in = mark_safe(self.lead_in)
-    self.save()
-
-
-Article.add_to_class('clean_article_html', clean_article_html)
-
-
 # Models
 
 class SubOrg(models.Model):
@@ -488,6 +469,12 @@ def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
 
 
+class BlogPostHistory(models.Model):
+    article = models.ForeignKey(Article, on_delete=models.CASCADE)
+    timestamp = models.DateTimeField(auto_now_add=True)
+    content = models.TextField(null=True, blank=True)
+
+
 class BlogPostDueDate(models.Model):
     categories = (
         (0, 'Weekly Check-In'),
@@ -1098,11 +1085,17 @@ def decrease_blog_counter(sender, instance, **kwargs):
             up.save()
 
 
-# Clean lead_in HTML when new Article is created
-# @receiver(models.signals.post_save, sender=Article)
-# def clean_html(sender, instance, **kwargs):
-#     if instance.is_unclean():
-#         instance.clean_article_html()
+# Add ArticleReveiw object when new Article is created
+@receiver(models.signals.post_save, sender=Article)
+def add_review(sender, instance, **kwargs):
+    ar = ArticleReview.objects.filter(article=instance).all()
+    if not ar:
+        ArticleReview.objects.create(article=instance)
+
+    if ar:
+        ar = ar.first()
+        ar.is_reviewed = False
+        ar.save()
 
 
 # Add ArticleReveiw object when new Article is created
@@ -1116,3 +1109,9 @@ def add_review(sender, instance, **kwargs):
         ar = ar.first()
         ar.is_reviewed = False
         ar.save()
+
+
+# Add BlogPostHistory object when new Article is created
+@receiver(models.signals.post_save, sender=Article)
+def add_history(sender, instance, **kwargs):
+    BlogPostHistory.objects.create(article=instance, content=instance.lead_in)
diff --git a/gsoc/static/css/article.css b/gsoc/static/css/article.css
index 0a6af621..322f813a 100644
--- a/gsoc/static/css/article.css
+++ b/gsoc/static/css/article.css
@@ -111,3 +111,65 @@ article > heading {
     text-decoration: none;
     color: white;
 }
+
+.aldryn-newsblog-comments {
+    margin: 16px 0;
+    padding: 16px 24px;
+}
+
+.aldryn-newsblog-comments form {
+    display: none;
+}
+
+.aldryn-newsblog-comments #form-root {
+    display: block;
+}
+
+.aldryn-newsblog-subcomments {
+    padding-left: 24px;
+    border-left: 0.1px solid #eee;
+}
+
+.comment {
+    padding: 5px 10px;
+    margin: 5px 0px;
+    background: #eee;
+}
+
+.comment.selected {
+    background: #ccc;
+}
+
+.comment-container .c-username {
+    color: #489EBA;
+    font-size: 12px;
+    font-weight: bold;
+}
+
+.comment-container .c-actions {
+    font-size: 12px;
+}
+
+.comment-container .c-actions span {
+    margin-right: 10px;
+}
+
+.comment-container .c-actions .reply {
+    cursor: pointer;
+}
+
+.comment-container .c-actions .share {
+    cursor: pointer;
+}
+
+.comment-container .c-actions .delete {
+    cursor: pointer;
+}
+
+.comment-container .c-content {
+    font-size: 14px;
+}
+
+form textarea {
+    border-radius: 0 0 4px 4px !important;
+}
diff --git a/gsoc/static/css/comments.css b/gsoc/static/css/comments.css
deleted file mode 100644
index 55077fc1..00000000
--- a/gsoc/static/css/comments.css
+++ /dev/null
@@ -1,61 +0,0 @@
-.aldryn-newsblog-comments {
-    margin: 16px 0;
-    padding: 16px 24px;
-}
-
-.aldryn-newsblog-comments form {
-    display: none;
-}
-
-.aldryn-newsblog-comments #form-root {
-    display: block;
-}
-
-.aldryn-newsblog-subcomments {
-    padding-left: 24px;
-    border-left: 0.1px solid #eee;
-}
-
-.comment {
-    padding: 5px 10px;
-    margin: 5px 0px;
-    background: #eee;
-}
-
-.comment.selected {
-    background: #ccc;
-}
-
-.comment-container .c-username {
-    color: #489EBA;
-    font-size: 12px;
-    font-weight: bold;
-}
-
-.comment-container .c-actions {
-    font-size: 12px;
-}
-
-.comment-container .c-actions span {
-    margin-right: 10px;
-}
-
-.comment-container .c-actions .reply {
-    cursor: pointer;
-}
-
-.comment-container .c-actions .share {
-    cursor: pointer;
-}
-
-.comment-container .c-actions .delete {
-    cursor: pointer;
-}
-
-.comment-container .c-content {
-    font-size: 14px;
-}
-
-form textarea {
-    border-radius: 0 0 4px 4px !important;
-}
\ No newline at end of file
diff --git a/gsoc/static/css/python-gsoc.css b/gsoc/static/css/python-gsoc.css
index 601c41d2..f3a8c7be 100644
--- a/gsoc/static/css/python-gsoc.css
+++ b/gsoc/static/css/python-gsoc.css
@@ -1,3 +1,260 @@
+body {
+    color: #777;
+}
+
+.pure-img-responsive {
+    max-width: 100%;
+    height: auto;
+}
+
+/*
+Add transition to containers so they can push in and out.
+*/
+#layout,
+#menu,
+.menu-link {
+    -webkit-transition: all 0.2s ease-out;
+    -moz-transition: all 0.2s ease-out;
+    -ms-transition: all 0.2s ease-out;
+    -o-transition: all 0.2s ease-out;
+    transition: all 0.2s ease-out;
+}
+
+/*
+This is the parent `
` that contains the menu and the content area. +*/ +#layout { + position: relative; + left: 0; + padding-left: 0; +} + #layout.active #menu { + left: 150px; + width: 150px; + } + + #layout.active .menu-link { + left: 150px; + } +/* +The content `
` is where all your content goes. +*/ +.content { + margin: 0 auto; + padding: 0 2em; + max-width: 800px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.content-blog { + margin: 0 auto; + padding: 0 2em; + max-width: 1600px; + margin-bottom: 50px; + line-height: 1.6em; +} + +.header { + margin: 0; + color: #333; + text-align: center; + padding: 2.5em 2em 0; + border-bottom: 1px solid #eee; + } + .header h1 { + margin: 0.2em 0; + font-size: 3em; + font-weight: 300; + } + .header h2 { + font-weight: 300; + color: #ccc; + padding: 0; + margin-top: 0; + } + +.content-subhead { + margin: 50px 0 20px 0; + font-weight: 300; + color: #888; +} + + + +/* +The `#menu` `
` is the parent `
` that contains the `.pure-menu` that +appears on the left side of the page. +*/ + +#menu { + margin-left: -150px; /* "#menu" width */ + width: 150px; + position: fixed; + top: 46px; + left: 0; + bottom: 0; + z-index: 1000; /* so the menu or its navicon stays above all content */ + background: #191818; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + /* + All anchors inside the menu should be styled like this. + */ + #menu a { + color: #999; + border: none; + padding: 0.6em 0 0.6em 0.6em; + } + + /* + Remove all background/borders, since we are applying them to #menu. + */ + #menu .pure-menu, + #menu .pure-menu ul { + border: none; + background: transparent; + } + + /* + Add that light border to separate items into groups. + */ + #menu .pure-menu ul, + #menu .pure-menu .menu-item-divided { + border-top: 1px solid #333; + } + /* + Change color of the anchor links on hover/focus. + */ + #menu .pure-menu li a:hover, + #menu .pure-menu li a:focus { + background: #333; + } + + /* + This styles the selected menu item `
  • `. + */ + #menu .pure-menu-selected, + #menu .pure-menu-heading { + background: #1f8dd6; + } + /* + This styles a link within a selected menu item `
  • `. + */ + #menu .pure-menu-selected a { + color: #fff; + } + + /* + This styles the menu heading. + */ + #menu .pure-menu-heading { + font-size: 110%; + color: #fff; + margin: 0; + } + +/* -- Dynamic Button For Responsive Menu -------------------------------------*/ + +/* +The button to open/close the Menu is custom-made and not part of Pure. Here's +how it works: +*/ + +/* +`.menu-link` represents the responsive menu toggle that shows/hides on +small screens. +*/ +.menu-link { + position: fixed; + display: block; /* show this only on small screens */ + top: 46px; + left: 0; /* "#menu width" */ + background: #000; + background: rgba(0,0,0,0.7); + font-size: 10px; /* change this value to increase/decrease button size */ + z-index: 10; + width: 2em; + height: auto; + padding: 2.1em 1.6em; +} + + .menu-link:hover, + .menu-link:focus { + background: #000; + } + + .menu-link span { + position: relative; + display: block; + } + + .menu-link span, + .menu-link span:before, + .menu-link span:after { + background-color: #fff; + width: 100%; + height: 0.2em; + } + + .menu-link span:before, + .menu-link span:after { + position: absolute; + margin-top: -0.6em; + content: " "; + } + + .menu-link span:after { + margin-top: 0.6em; + } + + +/* -- Responsive Styles (Media Queries) ------------------------------------- */ + +/* +Hides the menu at `48em`, but modify this based on your app's needs. +*/ +@media (min-width: 50em) { + + .header, + .content { + padding-left: 2em; + padding-right: 2em; + } + + #layout { + padding-left: 150px; /* left col width "#menu" */ + left: 0; + } + #menu { + left: 150px; + } + + .menu-link { + position: fixed; + left: 150px; + display: none; + } + + #layout.active .menu-link { + left: 150px; + } +} + +@media (max-width: 48em) { + /* Only apply this when the window is small. Otherwise, the following + case results in extra padding on the left: + * Make the window small. + * Tap the menu to trigger the active state. + * Make the window large again. + */ + #layout.active { + position: relative; + left: 150px; + } +} + h1, h2, h3, @@ -174,7 +431,7 @@ div.problem { #topnav .pure-menu-link.pure-menu-heading { margin-top: 6px; - color: #aaa; + color: #fff; } #topnav .pure-menu-link.pure-menu-heading:hover { @@ -207,6 +464,7 @@ div.problem { .blog-list .card-container .card .title { margin: 0.5em 0; + color: #23505D; } .blog-list .card-container .card .suborg { @@ -250,14 +508,23 @@ div.problem { .card .no-proposal { font-size: 12px; - color: red; + color: #990000; } .grecaptcha-badge { display: none; } + .center { display: flex; justify-content: center; align-items: center; - } \ No newline at end of file +} + +pre { + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} \ No newline at end of file diff --git a/gsoc/static/css/side-menu.css b/gsoc/static/css/side-menu.css deleted file mode 100644 index aeac3be3..00000000 --- a/gsoc/static/css/side-menu.css +++ /dev/null @@ -1,256 +0,0 @@ -body { - color: #777; -} - -.pure-img-responsive { - max-width: 100%; - height: auto; -} - -/* -Add transition to containers so they can push in and out. -*/ -#layout, -#menu, -.menu-link { - -webkit-transition: all 0.2s ease-out; - -moz-transition: all 0.2s ease-out; - -ms-transition: all 0.2s ease-out; - -o-transition: all 0.2s ease-out; - transition: all 0.2s ease-out; -} - -/* -This is the parent `
    ` that contains the menu and the content area. -*/ -#layout { - position: relative; - left: 0; - padding-left: 0; -} - #layout.active #menu { - left: 150px; - width: 150px; - } - - #layout.active .menu-link { - left: 150px; - } -/* -The content `
    ` is where all your content goes. -*/ -.content { - margin: 0 auto; - padding: 0 2em; - max-width: 800px; - margin-bottom: 50px; - line-height: 1.6em; -} - -.content-blog { - margin: 0 auto; - padding: 0 2em; - max-width: 1600px; - margin-bottom: 50px; - line-height: 1.6em; -} - -.header { - margin: 0; - color: #333; - text-align: center; - padding: 2.5em 2em 0; - border-bottom: 1px solid #eee; - } - .header h1 { - margin: 0.2em 0; - font-size: 3em; - font-weight: 300; - } - .header h2 { - font-weight: 300; - color: #ccc; - padding: 0; - margin-top: 0; - } - -.content-subhead { - margin: 50px 0 20px 0; - font-weight: 300; - color: #888; -} - - - -/* -The `#menu` `
    ` is the parent `
    ` that contains the `.pure-menu` that -appears on the left side of the page. -*/ - -#menu { - margin-left: -150px; /* "#menu" width */ - width: 150px; - position: fixed; - top: 46px; - left: 0; - bottom: 0; - z-index: 1000; /* so the menu or its navicon stays above all content */ - background: #191818; - overflow-y: auto; - -webkit-overflow-scrolling: touch; -} - /* - All anchors inside the menu should be styled like this. - */ - #menu a { - color: #999; - border: none; - padding: 0.6em 0 0.6em 0.6em; - } - - /* - Remove all background/borders, since we are applying them to #menu. - */ - #menu .pure-menu, - #menu .pure-menu ul { - border: none; - background: transparent; - } - - /* - Add that light border to separate items into groups. - */ - #menu .pure-menu ul, - #menu .pure-menu .menu-item-divided { - border-top: 1px solid #333; - } - /* - Change color of the anchor links on hover/focus. - */ - #menu .pure-menu li a:hover, - #menu .pure-menu li a:focus { - background: #333; - } - - /* - This styles the selected menu item `
  • `. - */ - #menu .pure-menu-selected, - #menu .pure-menu-heading { - background: #1f8dd6; - } - /* - This styles a link within a selected menu item `
  • `. - */ - #menu .pure-menu-selected a { - color: #fff; - } - - /* - This styles the menu heading. - */ - #menu .pure-menu-heading { - font-size: 110%; - color: #fff; - margin: 0; - } - -/* -- Dynamic Button For Responsive Menu -------------------------------------*/ - -/* -The button to open/close the Menu is custom-made and not part of Pure. Here's -how it works: -*/ - -/* -`.menu-link` represents the responsive menu toggle that shows/hides on -small screens. -*/ -.menu-link { - position: fixed; - display: block; /* show this only on small screens */ - top: 46px; - left: 0; /* "#menu width" */ - background: #000; - background: rgba(0,0,0,0.7); - font-size: 10px; /* change this value to increase/decrease button size */ - z-index: 10; - width: 2em; - height: auto; - padding: 2.1em 1.6em; -} - - .menu-link:hover, - .menu-link:focus { - background: #000; - } - - .menu-link span { - position: relative; - display: block; - } - - .menu-link span, - .menu-link span:before, - .menu-link span:after { - background-color: #fff; - width: 100%; - height: 0.2em; - } - - .menu-link span:before, - .menu-link span:after { - position: absolute; - margin-top: -0.6em; - content: " "; - } - - .menu-link span:after { - margin-top: 0.6em; - } - - -/* -- Responsive Styles (Media Queries) ------------------------------------- */ - -/* -Hides the menu at `48em`, but modify this based on your app's needs. -*/ -@media (min-width: 50em) { - - .header, - .content { - padding-left: 2em; - padding-right: 2em; - } - - #layout { - padding-left: 150px; /* left col width "#menu" */ - left: 0; - } - #menu { - left: 150px; - } - - .menu-link { - position: fixed; - left: 150px; - display: none; - } - - #layout.active .menu-link { - left: 150px; - } -} - -@media (max-width: 48em) { - /* Only apply this when the window is small. Otherwise, the following - case results in extra padding on the left: - * Make the window small. - * Tap the menu to trigger the active state. - * Make the window large again. - */ - #layout.active { - position: relative; - left: 150px; - } -} \ No newline at end of file diff --git a/gsoc/static/js/gsoc.js b/gsoc/static/js/gsoc.js deleted file mode 100644 index 200e408b..00000000 --- a/gsoc/static/js/gsoc.js +++ /dev/null @@ -1,9 +0,0 @@ -//js for register button -$(function() { - var chk = $('#check'); - var btn = $('#btncheck'); - - chk.on('change', function() { - btn.prop("disabled", !this.checked);//true: disabled, false: enabled - }).trigger('change'); //page load trigger event -}); diff --git a/gsoc/static/js/menu.js b/gsoc/static/js/menu.js deleted file mode 100644 index 7ace2710..00000000 --- a/gsoc/static/js/menu.js +++ /dev/null @@ -1,46 +0,0 @@ -(function (window, document) { - - var layout = document.getElementById('layout'), - menu = document.getElementById('menu'), - menuLink = document.getElementById('menuLink'), - content = document.getElementById('main'); - - function toggleClass(element, className) { - var classes = element.className.split(/\s+/), - length = classes.length, - i = 0; - - for(; i < length; i++) { - if (classes[i] === className) { - classes.splice(i, 1); - break; - } - } - // The className is not found - if (length === classes.length) { - classes.push(className); - } - - element.className = classes.join(' '); - } - - function toggleAll(e) { - var active = 'active'; - - e.preventDefault(); - toggleClass(layout, active); - toggleClass(menu, active); - toggleClass(menuLink, active); - } - - menuLink.onclick = function (e) { - toggleAll(e); - }; - - content.onclick = function(e) { - if (menu.className.indexOf('active') !== -1) { - toggleAll(e); - } - }; - -}(this, this.document)); \ No newline at end of file diff --git a/gsoc/static/js/python-gsoc.js b/gsoc/static/js/python-gsoc.js new file mode 100644 index 00000000..b9b69d83 --- /dev/null +++ b/gsoc/static/js/python-gsoc.js @@ -0,0 +1,56 @@ +//js for register button +$(function() { + var chk = $('#check'); + var btn = $('#btncheck'); + + chk.on('change', function() { + btn.prop("disabled", !this.checked);//true: disabled, false: enabled + }).trigger('change'); //page load trigger event +}); + +(function (window, document) { + + var layout = document.getElementById('layout'), + menu = document.getElementById('menu'), + menuLink = document.getElementById('menuLink'), + content = document.getElementById('main'); + + function toggleClass(element, className) { + var classes = element.className.split(/\s+/), + length = classes.length, + i = 0; + + for(; i < length; i++) { + if (classes[i] === className) { + classes.splice(i, 1); + break; + } + } + // The className is not found + if (length === classes.length) { + classes.push(className); + } + + element.className = classes.join(' '); + } + + function toggleAll(e) { + var active = 'active'; + + e.preventDefault(); + toggleClass(layout, active); + toggleClass(menu, active); + toggleClass(menuLink, active); + } + + menuLink.onclick = function (e) { + toggleAll(e); + }; + + content.onclick = function(e) { + if (menu.className.indexOf('active') !== -1) { + toggleAll(e); + } + }; + +}(this, this.document)); diff --git a/gsoc/templates/aldryn_newsblog/base.html b/gsoc/templates/aldryn_newsblog/base.html index 36151cbe..2e96c5ac 100644 --- a/gsoc/templates/aldryn_newsblog/base.html +++ b/gsoc/templates/aldryn_newsblog/base.html @@ -1,8 +1,8 @@ {% extends CMS_TEMPLATE %} {% block head %} + - {% endblock head %} @@ -14,19 +14,4 @@ {% block js %} - {% endblock %} \ No newline at end of file diff --git a/gsoc/templates/aldryn_newsblog/includes/comments.html b/gsoc/templates/aldryn_newsblog/includes/comments.html index a8456625..52fe9822 100644 --- a/gsoc/templates/aldryn_newsblog/includes/comments.html +++ b/gsoc/templates/aldryn_newsblog/includes/comments.html @@ -70,5 +70,7 @@ 1000 characters left +
    + \ No newline at end of file diff --git a/gsoc/templates/base.html b/gsoc/templates/base.html index bce302b0..ad7b23d3 100644 --- a/gsoc/templates/base.html +++ b/gsoc/templates/base.html @@ -1,23 +1,20 @@ {% load cms_tags menu_tags sekizai_tags %} - + Python GSoC - {% block title %}{% endblock %} - + - + - - - - {% load static %} {% block head %}{% endblock %} @@ -48,7 +45,7 @@ {% if not user.is_authenticated %}
    - Python-GSoC + Python-GSoC @@ -115,9 +112,7 @@
    {% render_block 'js' %} - - - + {% block js %}{% endblock %} diff --git a/gsoc/templates/myprofile.html b/gsoc/templates/myprofile.html index 5227da0e..71c3bc7b 100644 --- a/gsoc/templates/myprofile.html +++ b/gsoc/templates/myprofile.html @@ -6,86 +6,79 @@ {% block content %}
    -

    Welcome {% if not user.is_anonymous %}{{ user.username }} {% endif %}!

    - {% if user.is_anonymous %} -
    Please login here.
    - {% endif %} - - {% if user %} - {% if user.is_current_year_student %} - - - -{% endblock %} - -{% block js %} - + {% endif %} +
    {% endblock %} diff --git a/gsoc/templates/registration/change_password.html b/gsoc/templates/registration/change_password.html new file mode 100644 index 00000000..191c1052 --- /dev/null +++ b/gsoc/templates/registration/change_password.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %} +Change Password +{% endblock %} + +{% block content %} +
    +
    + +

    Change Password

    +
    + {% csrf_token %} + {{ form }} + +
    +
    +{% endblock %} \ No newline at end of file diff --git a/gsoc/templates/site/deadlines.html b/gsoc/templates/site/deadlines.html index 3d1d95b9..a82ed29f 100644 --- a/gsoc/templates/site/deadlines.html +++ b/gsoc/templates/site/deadlines.html @@ -28,6 +28,36 @@ + + @@ -90,7 +120,13 @@

    Blogging schedule (Student Deadlines)

    {% endfor %} - iCal Link +
    + +
    +
    + +
    + iCal Link

    Please note Google's GSoC dates and deadlines.

    @@ -99,6 +135,20 @@

    Blogging schedule (Student Deadlines)

  • + + + diff --git a/gsoc/urls.py b/gsoc/urls.py index c2daba4c..5e633740 100644 --- a/gsoc/urls.py +++ b/gsoc/urls.py @@ -33,7 +33,8 @@ urlpatterns += [ url('accounts/', include('django.contrib.auth.urls')), url('accounts/new', gsoc.views.new_account_view, name='new_account'), - url('accounts/register', gsoc.views.register_view, name='register') + url('accounts/register', gsoc.views.register_view, name='register'), + url('accounts/change_password', gsoc.views.change_password, name='change_password'), ] urlpatterns += i18n_patterns( diff --git a/gsoc/views.py b/gsoc/views.py index b9dd52c2..acedd9ab 100644 --- a/gsoc/views.py +++ b/gsoc/views.py @@ -1,5 +1,6 @@ from gsoc import settings +from .common.utils.memcached_stats import MemcachedStats from .forms import ProposalUploadForm from .models import (RegLink, ProposalTextValidator, Comment, ArticleReview, GsocYear) @@ -13,9 +14,11 @@ from django.contrib import messages from django.contrib.auth import decorators, password_validation, validators, logout from django.contrib.auth.models import User +from django.contrib.auth.forms import PasswordChangeForm from django import shortcuts from django.http import JsonResponse, HttpResponseRedirect from django.core.exceptions import ValidationError +from django.core.cache import cache from django.shortcuts import redirect from django.urls import reverse from django.conf import settings @@ -51,10 +54,11 @@ def upload_file(request): 'uploaded': 1, 'fileName': filename, 'url': fileurl - }) + }) # handle redirect to blogs + def redirect_blogs_list(request): return HttpResponseRedirect(f'/') @@ -68,6 +72,7 @@ def redirect_articles(request, blog_name, article_name): # handle proposal upload + def convert_pdf_to_txt(f): rsrcmgr = PDFResourceManager() retstr = io.StringIO() @@ -113,9 +118,7 @@ def scan_proposal(file): @decorators.login_required def after_login_view(request): user = request.user - if user.is_current_year_student() and not user.has_proposal(): - return shortcuts.redirect('/myprofile') - return shortcuts.redirect('/') + return shortcuts.redirect('/myprofile') @decorators.login_required @@ -262,29 +265,46 @@ def register_view(request): return shortcuts.render(request, 'registration/register.html', context) +@decorators.login_required +def change_password(request): + if request.method == 'POST': + form = PasswordChangeForm(request.user, request.POST) + if form.is_valid(): + user = form.save() + update_session_auth_hash(request, user) + messages.success(request, 'Your password was successfully updated!') + return redirect('change_password') + else: + messages.error(request, 'Please correct the error below.') + else: + form = PasswordChangeForm(request.user) + + return shortcuts.render(request, 'registration/change_password.html', { + 'form': form + }) + + def new_comment(request): if request.method == 'POST': # set environment variable `DISABLE_RECAPTCHA` to disable recaptcha # verification and delete the variable to enable recaptcha verification disable_recaptcha = os.getenv('DISABLE_RECAPTCHA', None) + flag = True if not disable_recaptcha: recaptcha_response = request.POST.get('g-recaptcha-response') url = 'https://www.google.com/recaptcha/api/siteverify' payload = { 'secret': settings.RECAPTCHA_PRIVATE_KEY, 'response': recaptcha_response - } + } data = urllib.parse.urlencode(payload).encode() req = urllib.request.Request(url, data=data) response = urllib.request.urlopen(req) result = json.loads(response.read().decode()) - flag = True - if not disable_recaptcha: - flag = (result['success'] and result['action'] == 'comment' - and result['score'] >= settings.RECAPTCHA_THRESHOLD) + flag = result['success'] if flag: # if score greater than threshold allow to add @@ -322,6 +342,13 @@ def new_comment(request): redirect_path = request.POST.get('redirect') + mem = MemcachedStats() + keys = [_[3:] for _ in mem.keys()] + for key in keys: + if 'cache_page' in key or 'cache_header' in key: + print(key, cache.get(key)) + cache.delete(key) + if redirect_path: return redirect(redirect_path) else: @@ -383,4 +410,3 @@ def publish_article(request, article_id): else: messages.error(request, 'User does not have permission to publish article') return redirect(reverse('{}:article-detail'.format(a.app_config.namespace), args=[a.slug])) - diff --git a/project.db b/project.db index 91239641..52e1857f 100644 Binary files a/project.db and b/project.db differ diff --git a/requirements.txt b/requirements.txt index 21e0da73..3d31a7dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,5 +41,5 @@ google-auth-oauthlib>=0.3.0 #github api for pushing html pages PyGithub>=1.43.7 -#memcached -#pylibmc>=1.6.0 +python-memcached +pylibmc>=1.6.0 diff --git a/settings_local.py.template b/settings_local.py.template index c7df19a8..43bf63cc 100644 --- a/settings_local.py.template +++ b/settings_local.py.template @@ -51,9 +51,8 @@ ADMIN_EMAIL = 'gsoc-admins@python.org' # reCAPTCHA settings # update the `RECAPTCHA_PUBLIC_KEY` in `/static/js/recaptcha.js` manually -RECAPTCHA_PRIVATE_KEY = '6LdAVqIUAAAAACv_tNPudwx_pD9dbjtwvr3WwQ9Y' -RECAPTCHA_PUBLIC_KEY = '6LdAVqIUAAAAAAt6baSHpGXr1LvJ0n1aCl_oqukj' -RECAPTCHA_THRESHOLD = 0.1 +RECAPTCHA_PRIVATE_KEY = '6LcL0q8UAAAAAFPz31u0Ce9gnbEjhFou19c4MhnQ' +RECAPTCHA_PUBLIC_KEY = '6LcL0q8UAAAAALYynEklThsKgSVZ2B1kubc-Y6br' # google oauth client_config GOOGLE_API_SCOPES = [