Skip to content

Commit

Permalink
Add hackathon projects (#5414)
Browse files Browse the repository at this point in the history
* add hackathon projects

* add templates

* fix isort

* add show/hide project page button

* add save/create and logic

* upgrade select2

* migrations update

* fix query by standard bounty id and validations

* add project adition modal to fullfilment success

* pagination and missing change

* secure img upload backend

* review fixes

* review comments
  • Loading branch information
octavioamu authored and danlipert committed Nov 6, 2019
1 parent 98a1a15 commit d31397a
Show file tree
Hide file tree
Showing 18 changed files with 764 additions and 16 deletions.
4 changes: 4 additions & 0 deletions app/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@
# Hackathons / special events
path('hackathon/<str:hackathon>/', dashboard.views.hackathon, name='hackathon'),
path('hackathon/onboard/<str:hackathon>/', dashboard.views.hackathon_onboard, name='hackathon_onboard'),
path('hackathon/projects/<str:hackathon>/', dashboard.views.hackathon_projects, name='hackathon_projects'),
path('modal/new_project/<int:bounty_id>/', dashboard.views.hackathon_get_project, name='hackathon_get_project'),
path('modal/new_project/<int:bounty_id>/<int:project_id>/', dashboard.views.hackathon_get_project, name='hackathon_edit_project'),
path('modal/save_project/', dashboard.views.hackathon_save_project, name='hackathon_save_project'),
re_path(r'^hackathon/?$/?', dashboard.views.hackathon, name='hackathon_idx'),
re_path(r'^hackathon/(.*)?$', dashboard.views.hackathon, name='hackathon_idx2'),
path('hackathon-list/', dashboard.views.get_hackathons, name='get_hackathons'),
Expand Down
4 changes: 0 additions & 4 deletions app/assets/v2/css/forms/select.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@
width: 100%;
}

.form__select2 select {
display: none;
}

.form__select select[multiple] {
height: auto;
}
Expand Down
2 changes: 1 addition & 1 deletion app/assets/v2/css/jquery.select2.min.css

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions app/assets/v2/js/hackathon-projects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// document.result.pk
const projectModal = (bountyId, projectId) => {
$('#modalProject').bootstrapModal('hide');
const modalUrl = projectId ? `/modal/new_project/${bountyId}/${projectId}/` : `/modal/new_project/${bountyId}/`;

$.ajax({
url: modalUrl,
type: 'GET',
cache: false
}).done(function(result) {
$('body').append(result);
let data = $('.team-users').data('initial') ? $('.team-users').data('initial').split(', ') : [];

userSearch('.team-users', false, '', data, true, false);
$('#modalProject').bootstrapModal('show');
$('[data-toggle="tooltip"]').bootstrapTooltip();

$('#projectForm').on('submit', function(e) {
e.preventDefault();
let logo = $(this)[0]['logo'].files[0];
let formData = new FormData();
let data = $(this).serializeArray();

formData.append('logo', logo);

for (let i = 0; i < data.length; i++) {
formData.append(data[i].name, data[i].value);
}

const sendConfig = {
url: '/modal/save_project/',
method: 'POST',
data: formData,
processData: false,
dataType: 'json',
contentType: false
};

$.ajax(sendConfig).done(function(response) {
if (!response.success) {
return _alert(response.msg, 'error');
}
delete localStorage['pendingProject'];
$('#modalProject').bootstrapModal('hide');
return _alert({message: response.msg}, 'info');

}).fail(function(data) {
_alert(data.responseJSON['error'], 'error');
});

});
});

$(document).on('change', '#project_logo', function() {
previewFile($(this));
});
};

$(document, '#modalProject').on('hide.bs.modal', function(e) {
$('#modalProject').remove();
$('#modalProject').bootstrapModal('dispose');
});

const previewFile = function(elem) {
let preview = document.querySelector('#img-preview');
let file = elem[0].files[0];
let reader = new FileReader();

reader.onloadend = function() {
let imageURL = reader.result;

getImageSize(imageURL, function(imageWidth, imageHeight) {
if (imageWidth !== imageHeight) {
elem.val('');
preview.src = elem.data('imgplaceholder');
return alert('Please use a square image');
}
preview.src = reader.result;
});
};

if (file) {
reader.readAsDataURL(file);
}
};

function getImageSize(imageURL, callback) {
let image = new Image();

image.onload = function() {
callback(this.naturalWidth, this.naturalHeight);
};
image.src = imageURL;
}
3 changes: 2 additions & 1 deletion app/assets/v2/js/lib/jquery.select2.min.js

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions app/assets/v2/js/pages/bounty_details.js
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,9 @@ var show_interest_modal = function() {
$(self).find('span').text(gettext('Stop Work'));
$(self).parent().attr('title', '<div class="tooltip-info tooltip-sm">' + gettext('Notify the funder that you will not be working on this project') + '</div>');
modals.bootstrapModal('hide');
if (document.result.event) {
projectModal(document.result.pk);
}
}
}).catch((error) => {
if (error.responseJSON.error === 'You may only work on max of 3 issues at once.')
Expand Down Expand Up @@ -1469,6 +1472,10 @@ var pull_bounty_from_api = function() {

document.result = result;

if (document.result.event && localStorage['pendingProject']) {
projectModal(document.result.pk);
}

if (typeof promptPrivateInstructions !== 'undefined' && result.repo_type === 'private') {
repoInstructions();
}
Expand Down
2 changes: 2 additions & 0 deletions app/assets/v2/js/pages/fulfill_bounty.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ window.onload = function() {
txid: result
});

localStorage['pendingProject'] = true;

var finishedComment = function() {
dataLayer.push({ event: 'claimissue' });
_alert({ message: gettext('Fulfillment submitted to web3.') }, 'info');
Expand Down
34 changes: 31 additions & 3 deletions app/dashboard/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@

from .models import (
Activity, BlockedUser, Bounty, BountyFulfillment, BountyInvites, BountySyncRequest, CoinRedemption,
CoinRedemptionRequest, Coupon, Earning, FeedbackEntry, HackathonEvent, HackathonRegistration, HackathonSponsor,
Interest, LabsResearch, PortfolioItem, Profile, ProfileView, RefundFeeRequest, SearchHistory, Sponsor, Tip,
TokenApproval, Tool, ToolVote, UserAction, UserVerificationModel,
CoinRedemptionRequest, Coupon, Earning, FeedbackEntry, HackathonEvent, HackathonProject, HackathonRegistration,
HackathonSponsor, Interest, LabsResearch, PortfolioItem, Profile, ProfileView, RefundFeeRequest, SearchHistory,
Sponsor, Tip, TokenApproval, Tool, ToolVote, UserAction, UserVerificationModel,
)


Expand Down Expand Up @@ -362,6 +362,33 @@ class HackathonRegistrationAdmin(admin.ModelAdmin):
list_display = ['pk', 'name', 'referer', 'registrant']
raw_id_fields = ['registrant']


class HackathonProjectAdmin(admin.ModelAdmin):
list_display = ['pk', 'img', 'name', 'bounty', 'hackathon', 'usernames', 'status', 'sponsor']
raw_id_fields = ['profiles', 'bounty', 'hackathon']
search_fields = ['name', 'summary', 'status']

def img(self, instance):
"""Returns a formatted HTML img node or 'n/a' if the HackathonProject has no logo.
Returns:
str: A formatted HTML img node or 'n/a' if the HackathonProject has no logo.
"""
logo = instance.logo
if not logo:
return 'n/a'
img_html = format_html('<img src={} style="max-width:30px; max-height: 30px">', mark_safe(logo.url))
return img_html

def usernames(self, obj):
"""Get the profile handle."""
return "\n".join([p.handle for p in obj.profiles.all()])

def sponsor(self, obj):
"""Get the profile handle."""
return obj.bounty.org_name


admin.site.register(SearchHistory, SearchHistoryAdmin)
admin.site.register(Activity, ActivityAdmin)
admin.site.register(Earning, EarningAdmin)
Expand All @@ -385,6 +412,7 @@ class HackathonRegistrationAdmin(admin.ModelAdmin):
admin.site.register(HackathonEvent, HackathonEventAdmin)
admin.site.register(HackathonSponsor, HackathonSponsorAdmin)
admin.site.register(HackathonRegistration, HackathonRegistrationAdmin)
admin.site.register(HackathonProject, HackathonProjectAdmin)
admin.site.register(FeedbackEntry, FeedbackAdmin)
admin.site.register(LabsResearch)
admin.site.register(UserVerificationModel, VerificationAdmin)
Expand Down
36 changes: 36 additions & 0 deletions app/dashboard/migrations/0061_hackathonproject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 2.2.4 on 2019-11-05 00:29

import app.utils
from django.db import migrations, models
import django.db.models.deletion
import economy.models


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0060_auto_20191023_1430'),
]

operations = [
migrations.CreateModel(
name='HackathonProject',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)),
('modified_on', models.DateTimeField(default=economy.models.get_time)),
('name', models.CharField(max_length=255)),
('logo', models.ImageField(blank=True, help_text='Project Logo', null=True, upload_to=app.utils.get_upload_filename)),
('work_url', models.URLField(help_text='Repo or PR url')),
('summary', models.TextField(blank=True, default='')),
('badge', models.URLField(blank=True, db_index=True, help_text='badge img url', null=True)),
('status', models.CharField(blank=True, choices=[('invalid', 'invalid'), ('pending', 'pending'), ('accepted', 'accepted'), ('completed', 'completed')], max_length=20)),
('bounty', models.ForeignKey(help_text='bounty prize url', on_delete=django.db.models.deletion.CASCADE, related_name='project_bounty', to='dashboard.Bounty')),
('hackathon', models.ForeignKey(help_text='Hackathon event', on_delete=django.db.models.deletion.CASCADE, related_name='project_event', to='dashboard.HackathonEvent')),
('profiles', models.ManyToManyField(related_name='project_profiles', to='dashboard.Profile')),
],
options={
'ordering': ['-name'],
},
),
]
18 changes: 18 additions & 0 deletions app/dashboard/migrations/0062_hackathonevent_show_results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-11-05 00:29

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0061_hackathonproject'),
]

operations = [
migrations.AddField(
model_name='hackathonevent',
name='show_results',
field=models.BooleanField(default=True, help_text='Hide/Show the links to access hackathon results'),
),
]
53 changes: 53 additions & 0 deletions app/dashboard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3988,6 +3988,7 @@ class HackathonEvent(SuperModel):
text_color = models.CharField(max_length=255, null=True, blank=True, help_text='hexcode for the text, default to black')
identifier = models.CharField(max_length=255, default='', help_text='used for custom styling for the banner')
sponsors = models.ManyToManyField(Sponsor, through='HackathonSponsor')
show_results = models.BooleanField(help_text=_('Hide/Show the links to access hackathon results'), default=True)

def __str__(self):
"""String representation for HackathonEvent.
Expand Down Expand Up @@ -4033,6 +4034,58 @@ class HackathonSponsor(SuperModel):
default='G',
)


class HackathonProject(SuperModel):
PROJECT_STATUS = [
('invalid', 'invalid'),
('pending', 'pending'),
('accepted', 'accepted'),
('completed', 'completed'),
]
name = models.CharField(max_length=255)
hackathon = models.ForeignKey(
'HackathonEvent',
related_name='project_event',
on_delete=models.CASCADE,
help_text='Hackathon event'
)
logo = models.ImageField(
upload_to=get_upload_filename,
null=True,
blank=True,
help_text=_('Project Logo')
)
profiles = models.ManyToManyField(
'dashboard.Profile',
related_name='project_profiles',
)
work_url = models.URLField(help_text='Repo or PR url')
summary = models.TextField(default='', blank=True)
bounty = models.ForeignKey(
'dashboard.Bounty',
related_name='project_bounty',
on_delete=models.CASCADE,
help_text='bounty prize url'
)
badge = models.URLField(
blank=True,
null=True,
db_index=True,
help_text='badge img url'
)
status = models.CharField(
max_length=20,
choices=PROJECT_STATUS,
blank=True
)

class Meta:
ordering = ['-name']

def __str__(self):
return f"{self.name} - {self.bounty} on {self.created_on}"


class FeedbackEntry(SuperModel):
bounty = models.ForeignKey(
'dashboard.Bounty',
Expand Down
1 change: 1 addition & 0 deletions app/dashboard/templates/bounty/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ <h3>{{ noscript.keywords }}</h3>
<script src="{% static "v2/js/pages/bounty_funder_payout_reminder.js" %}"></script>
<script src="{% static "v2/js/pages/bounty_share.js" %}"></script>
<script src="{% static "v2/js/pages/bounty_details.js" %}"></script>
<script src="{% static "v2/js/hackathon-projects.js" %}"></script>
<script src="{% static "v2/js/user_popover.js" %}"></script>
<script src="{% static "v2/js/lib/ipfs-api.js" %}"></script>
<script src="{% static "v2/js/ipfs.js" %}"></script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,12 @@ <h5 class="card-title"><a href="{% url 'hackathon' hackathon.slug %}">{{ hackath
</div>
<div class="text-center mt-3">
{% if hackathon.end_date|timesince <= "1 min" %}
<a href="{% url 'hackathon_onboard' hackathon.slug %}" class="btn btn-gc-blue">Join</a>
<a href="{% url 'hackathon_onboard' hackathon.slug %}" class="btn btn-gc-blue">Join</a>
{% endif %}
<a href="{% url 'hackathon' hackathon.slug %}" class="btn btn-gc-blue">Prizes</a>
{% if hackathon.show_results %}
<a href="{% url 'hackathon_projects' hackathon.slug %}" class="btn btn-gc-blue">Projects</a>
{% endif %}
</div>
</div>
</div>
Expand All @@ -81,5 +84,5 @@ <h5 class="card-title"><a href="{% url 'hackathon' hackathon.slug %}">{{ hackath
{% include 'shared/analytics.html' %}
{% include 'shared/footer_scripts.html' %}
{% include 'shared/footer.html' %}

</body>
Loading

0 comments on commit d31397a

Please sign in to comment.