From 039133fade974bcabf508a4499e7f5133bf0f810 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Mon, 7 May 2018 22:04:03 +0530 Subject: [PATCH 01/32] WIP on jobs board --- app/app/settings.py | 1 + app/app/urls.py | 8 + app/assets/v2/css/jobs.css | 155 ++++ app/assets/v2/js/pages/jobs.js | 742 ++++++++++++++++++ app/jobs/__init__.py | 0 app/jobs/admin.py | 8 + app/jobs/api.py | 12 + app/jobs/apps.py | 5 + app/jobs/forms.py | 35 + app/jobs/migrations/0001_initial.py | 35 + .../migrations/0002_auto_20180505_1435.py | 23 + app/jobs/migrations/__init__.py | 0 app/jobs/models.py | 58 ++ app/jobs/routers.py | 7 + app/jobs/serializers.py | 16 + app/jobs/templates/jobs/detail.html | 311 ++++++++ app/jobs/templates/jobs/list.html | 141 ++++ app/jobs/templates/jobs/sidebar_search.html | 87 ++ app/jobs/tests.py | 3 + app/jobs/views.py | 22 + 20 files changed, 1669 insertions(+) create mode 100644 app/assets/v2/css/jobs.css create mode 100644 app/assets/v2/js/pages/jobs.js create mode 100644 app/jobs/__init__.py create mode 100644 app/jobs/admin.py create mode 100644 app/jobs/api.py create mode 100644 app/jobs/apps.py create mode 100644 app/jobs/forms.py create mode 100644 app/jobs/migrations/0001_initial.py create mode 100644 app/jobs/migrations/0002_auto_20180505_1435.py create mode 100644 app/jobs/migrations/__init__.py create mode 100644 app/jobs/models.py create mode 100644 app/jobs/routers.py create mode 100644 app/jobs/serializers.py create mode 100644 app/jobs/templates/jobs/detail.html create mode 100644 app/jobs/templates/jobs/list.html create mode 100644 app/jobs/templates/jobs/sidebar_search.html create mode 100644 app/jobs/tests.py create mode 100644 app/jobs/views.py diff --git a/app/app/settings.py b/app/app/settings.py index 1008712c01f..76bd0a9458a 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -87,6 +87,7 @@ 'gitcoinbot', 'external_bounties', 'dataviz', + 'jobs', ] MIDDLEWARE = [ diff --git a/app/app/urls.py b/app/app/urls.py index e8b211db32d..538fd22352b 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -39,8 +39,10 @@ import retail.emails import retail.views import tdi.views +import jobs.views from dashboard.router import router as dbrouter from external_bounties.router import router as ebrouter +from jobs.routers import router as job_router from .sitemaps import sitemaps @@ -54,6 +56,7 @@ url(r'^api/v0.1/faucet/save/?', faucet.views.save_faucet, name='save_faucet'), url(r'^api/v0.1/', include(dbrouter.urls)), url(r'^api/v0.1/', include(ebrouter.urls)), + url(r'^api/v0.1/', include(job_router.urls)), url(r'^actions/api/v0.1/', include(dbrouter.urls)), # same as active, but not cached in cluodfront # dashboard views @@ -246,6 +249,11 @@ path(settings.SENDGRID_EVENT_HOOK_URL, marketing.webhookviews.process, name='sendgrid_event_process'), # gitcoinbot url(settings.GITHUB_EVENT_HOOK_URL, gitcoinbot.views.payload, name='payload'), + + + # Job urls goes below + path('jobs', jobs.views.list_jobs, name='jobs'), + path('jobs//', jobs.views.job_detail, name='jobs'), ] if settings.ENABLE_SILK: diff --git a/app/assets/v2/css/jobs.css b/app/assets/v2/css/jobs.css new file mode 100644 index 00000000000..ae4f9c8cb05 --- /dev/null +++ b/app/assets/v2/css/jobs.css @@ -0,0 +1,155 @@ +body { + font-family: 'Muli', sans-serif; + background: #FFFFFF; +} + +.invisible { + background: #FFFFFF !important; + visibility: hidden; +} + +#onboard-dashboard { + background: #E8FFF2; + padding-top: 2em; + padding-bottom: 1em; + margin-bottom: 2em; +} + +#onboard-dashboard h2, { + font-weight: bolder; +} + +#onboard-dashboard h2, +#onboard-dashboard h3 { + text-transform: capitalize; + letter-spacing: 0; +} + +#onboard-dashboard a { + float: right; + padding-left: 25px; + padding-right: 25px; +} + +.title-row { + display: flex; + margin-top: 2em; + padding-bottom: 5px; + border-bottom: 2px solid #3E24FB; +} + +#filter { + color: #3e24fb; +} + +.bounty-info { + top: 4px; + text-align: right; +} + +#stats { + padding-left: 5px; + font-family: 'Muli', sans-serif; + font-weight: 300; + color: #7A7A7A; +} + +#matches { + font-family: 'Muli', sans-serif; + font-weight: 300; + padding-left: 10px; +} + +.title { + text-transform: capitalize; +} + +.bounty_row { + color: #000000; + text-decoration: none; + padding: 10px 20px 10px 4em; +} + +.bounty_row .avatar-container { + text-align: right; +} + +.bounty_row img { + margin-top: 6px; + margin-right: 20px; +} + +.bounty_row .title { + margin-bottom: 2px; +} + +.bounty_row:hover { + color: #000000; + text-decoration: none; + background-color: #F9F9F9; +} + +.bounty-detail { + margin-top: 10px; + margin-bottom: 10px; +} + +.bounty-summary { + display: inline-flex; +} + +.bounty-summary img { + position: absolute; + width: 20px; + margin: 0; + top: 2px; +} + +.bounty-summary .col-xs-1 { + margin-right: 2em; +} + +.bounty-summary .github-comment { + position: absolute; + text-align: center; + color: white; + width: 20px; + top: 0px; +} + +.bounty-detail .info { + font-family: 'Muli', sans-serif; + font-weight: 300; + color: #666666; +} + +.tags { + display: flex; +} + +@media (max-width: 768px) { + + #onboard-dashboard .col-12 { + padding-left: 3em; + padding-right: 3em; + } + + #filter { + padding-left: 20px; + } + + .bounty_row { + padding-left: 1em; + padding-right: 2em; + } + + .bounty-info { + top: 10px; + font-size: 10px; + text-align: left; + } + + #stats { + font-size: 10px; + } +} diff --git a/app/assets/v2/js/pages/jobs.js b/app/assets/v2/js/pages/jobs.js new file mode 100644 index 00000000000..1a44e8334fe --- /dev/null +++ b/app/assets/v2/js/pages/jobs.js @@ -0,0 +1,742 @@ +/* eslint-disable no-loop-func */ +// helper functions +var technologies = [ + '.NET', 'ASP .NET', 'Angular', 'Backbone', 'Bootstrap', 'C', 'C#', 'C++', 'CSS', 'CSS3', + 'CoffeeScript', 'Dart', 'Django', 'Drupal', 'DynamoDB', 'ElasticSearch', 'Ember', 'Erlang', 'Express', 'Go', 'Groovy', + 'Grunt', 'HTML', 'Hadoop', 'Jasmine', 'Java', 'JavaScript', 'Jekyll', 'Knockout', 'LaTeX', 'Mocha', 'MongoDB', + 'MySQL', 'NoSQL', 'Node.js', 'Objective-C', 'Oracle', 'PHP', 'Perl', 'Polymer', 'Postgres', 'Python', 'R', 'Rails', + 'React', 'Redis', 'Redux', 'Ruby', 'SASS', 'Scala', 'Sqlite', 'Swift', 'TypeScript', 'Websockets', 'WordPress', 'jQuery' +]; + +var sidebar_keys = [ + 'experience_level', + 'project_length', + 'bounty_type', + 'bounty_filter', + 'network', + 'idx_status', + 'tech_stack' +]; + +var localStorage; + +try { + localStorage = window.localStorage; +} catch (e) { + localStorage = {}; +} + +function debounce(func, wait, immediate) { + var timeout; + + return function() { + var context = this; + var args = arguments; + var later = function() { + timeout = null; + if (!immediate) + func.apply(context, args); + }; + var callNow = immediate && !timeout; + + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) + func.apply(context, args); + }; +} + +// sets search information default +var save_sidebar_latest = function() { + localStorage['order_by'] = $('#sort_option').val(); + + for (var i = 0; i < sidebar_keys.length; i++) { + var key = sidebar_keys[i]; + + if (key !== 'tech_stack') { + localStorage[key] = $('input[name=' + key + ']:checked').val(); + } else { + localStorage[key] = ''; + + $('input[name=' + key + ']:checked').each(function() { + localStorage[key] += $(this).val() + ','; + }); + + // Removing the start and last comma to avoid empty element when splitting with comma + localStorage[key] = localStorage[key].replace(/^,|,\s*$/g, ''); + } + } +}; + +// saves search information default +var set_sidebar_defaults = function() { + + // Special handling to support adding keywords from url query param + var q = getParam('q'); + var keywords; + + if (q) { + keywords = decodeURIComponent(q).replace(/^,|\s|,\s*$/g, ''); + + if (localStorage['keywords']) { + keywords.split(',').forEach(function(v, k) { + if (localStorage['keywords'].indexOf(v) === -1) { + localStorage['keywords'] += ',' + v; + } + }); + } else { + localStorage['keywords'] = keywords; + } + + window.history.replaceState(history.state, 'Issue Explorer | Gitcoin', '/explorer'); + } + + if (localStorage['order_by']) { + $('#sort_option').val(localStorage['order_by']); + $('#sort_option').selectmenu('refresh'); + } + + for (var i = 0; i < sidebar_keys.length; i++) { + var key = sidebar_keys[i]; + + if (localStorage[key]) { + if (key !== 'tech_stack') { + $('input[name=' + key + '][value=' + localStorage[key] + ']').prop('checked', true); + } else { + localStorage[key].split(',').forEach(function(v, k) { + $('input[name=' + key + '][value=' + v + ']').prop('checked', true); + }); + } + } + } +}; + +var set_filter_header = function() { + var idxStatusEl = $('input[name=idx_status]:checked'); + var filter_status = idxStatusEl.attr('val-ui') ? idxStatusEl.attr('val-ui') : 'All'; + // TODO: See what all filters are to be displayed from designs + $('#filter').html("All"); +}; + +var toggleAny = function(event) { + if (!event) + return; + // var key = event.target.name; + // var anyOption = $('input[name=' + key + '][value=any]'); + + // // Selects option 'any' when no filter is applied + // if ($('input[name=' + key + ']:checked').length === 0) { + // anyOption.prop('checked', true); + // return; + // } + // if (event.target.value === 'any') { + // // Deselect other filters when 'any' is selected + // $('input[name=' + key + '][value!=any]').prop('checked', false); + // } else { + // // Deselect option 'any' when another filter is selected + // anyOption.prop('checked', false); + // } +}; + +var getFilters = function() { + var _filters = []; + + for (var i = 0; i < sidebar_keys.length; i++) { + var key = sidebar_keys[i]; + + $.each($('input[name=' + key + ']:checked'), function() { + if ($(this).attr('val-ui')) { + _filters.push('' + $(this).attr('val-ui') + '' + + ''); + } + }); + } + + if (localStorage['keywords']) { + localStorage['keywords'].split(',').forEach(function(v, k) { + _filters.push('' + v + '' + + ''); + }); + } + + $('.filter-tags').html(_filters); +}; + +var removeFilter = function(key, value) { + if (key !== 'keywords') { + $('input[name=' + key + '][value=' + value + ']').prop('checked', false); + } else { + localStorage['keywords'] = localStorage['keywords'].replace(value, '').replace(',,', ','); + + // Removing the start and last comma to avoid empty element when splitting with comma + localStorage['keywords'] = localStorage['keywords'].replace(/^,|,\s*$/g, ''); + } + + refreshjobs(); +}; + +var get_search_URI = function() { + var uri = '/api/v0.1/jobs/?'; + var keywords = ''; + + for (var i = 0; i < sidebar_keys.length; i++) { + var key = sidebar_keys[i]; + var filters = []; + + $.each ($('input[name=' + key + ']:checked'), function() { + if (key === 'tech_stack' && $(this).val()) { + keywords += $(this).val() + ', '; + } else if ($(this).val()) { + filters.push($(this).val()); + } + }); + + var val = filters.toString(); + + if ((key === 'bounty_filter') && val) { + var values = val.split(','); + + values.forEach(function(_value) { + var _key; + + if (_value === 'createdByMe') { + _key = 'bounty_owner_github_username'; + _value = document.contxt.github_handle; + } else if (_value === 'startedByMe') { + _key = 'interested_github_username'; + _value = document.contxt.github_handle; + } else if (_value === 'fulfilledByMe') { + _key = 'fulfiller_github_username'; + _value = document.contxt.github_handle; + } + + if (_value !== 'any') + uri += '&' + _key + '=' + _value; + }); + + // TODO: Check if value myself is needed for coinbase + if (val === 'fulfilledByMe') { + key = 'bounty_owner_address'; + val = 'myself'; + } + } + + if (val !== 'any' && + key !== 'bounty_filter' && + key !== 'bounty_owner_address') { + uri += '&' + key + '=' + val; + } + } + + if (localStorage['keywords']) { + localStorage['keywords'].split(',').forEach(function(v, k) { + keywords += v + ', '; + }); + } + + if (keywords) { + uri += '&raw_data=' + keywords; + } + + if (typeof web3 != 'undefined' && web3.eth.coinbase) { + uri += '&coinbase=' + web3.eth.coinbase; + } else { + uri += '&coinbase=unknown'; + } + + var order_by = localStorage['order_by']; + + if (order_by) { + uri += '&order_by=' + order_by; + } + + return uri; +}; + +var process_stats = function(results) { + var num = results.length; + var worth_usdt = 0; + var worth_eth = 0; + var currencies_to_value = {}; + + for (var i = 0; i < results.length; i++) { + var result = results[i]; + + var this_worth_usdt = Number.parseFloat(result['value_in_usdt']); + var this_worth_eth = Number.parseFloat(result['value_in_eth']); + + if (this_worth_usdt) { + worth_usdt += this_worth_usdt; + } + if (this_worth_eth) { + worth_eth += this_worth_eth; + } + var token = result['token_name']; + + if (token !== 'ETH') { + if (!currencies_to_value[token]) { + currencies_to_value[token] = 0; + } + currencies_to_value[token] += Number.parseFloat(result['value_true']); + } + } + + worth_usdt = worth_usdt.toFixed(2); + worth_eth = (worth_eth / Math.pow(10, 18)).toFixed(2); + var stats = worth_usdt + ' USD, ' + worth_eth + ' ETH'; + + for (var t in currencies_to_value) { + if (Object.prototype.hasOwnProperty.call(currencies_to_value, t)) { + stats += ', ' + currencies_to_value[t].toFixed(2) + ' ' + t; + } + } + + var matchesEl = $('#matches'); + var + listingInfoEl = $('#listing-info'); + + switch (num) { + case 0: + matchesEl.html(gettext('No Results')); + + listingInfoEl.html(''); + break; + case 1: + // matchesEl.html(num + gettext(' Matching Result')); + // + listingInfoEl.html("Funded Issue(" + stats + ')'); + + listingInfoEl.html(num + gettext(' Active Job Listing')); + break; + default: + // matchesEl.html(num + gettext(' Matching Results')); + // + listingInfoEl.html("Funded Issues(" + stats + ')'); + // + listingInfoEl.html("Funded Issues(" + stats + ')'); + + listingInfoEl.html(num + gettext(' Active Job Listings')); + } +}; + +var paint_jobs_in_viewport = function(start, max) { + document.is_painting_now = true; + var num_jobs = document.jobs_html.length; + + for (var i = start; i < num_jobs && i < max; i++) { + var html = document.jobs_html[i]; + + document.last_bounty_rendered = i; + $('#jobs').append(html); + } + + $('div.bounty_row.result').each(function() { + var href = $(this).attr('href'); + + if (typeof $(this).changeElementType !== 'undefined') { + $(this).changeElementType('a'); // hack so that users can right click on the element + } + + $(this).attr('href', href); + }); + document.is_painting_now = false; + + if (document.referrer.search('/onboard') != -1) { + $('.bounty_row').each(function(index) { + if (index > 2) + $(this).addClass('hidden'); + }); + } +}; + +var trigger_scroll = debounce(function() { + if (typeof document.jobs_html == 'undefined' || document.jobs_html.length == 0) { + return; + } + var scrollPos = $(document).scrollTop(); + var last_active_bounty = $('.bounty_row.result:last-child'); + + if (last_active_bounty.length == 0) { + return; + } + + var window_height = $(window).height(); + var have_painted_all_jobs = document.jobs_html.length <= document.last_bounty_rendered; + var buffer = 500; + var does_need_to_paint_more = !document.is_painting_now && !have_painted_all_jobs && ((last_active_bounty.offset().top) < (scrollPos + buffer + window_height)); + + if (does_need_to_paint_more) { + paint_jobs_in_viewport(document.last_bounty_rendered + 1, document.last_bounty_rendered + 6); + } +}, 200); + +$(window).scroll(trigger_scroll); +$('body').bind('touchmove', trigger_scroll); + +var refreshjobs = function(event) { + save_sidebar_latest(); + set_filter_header(); + toggleAny(event); + getFilters(); + + $('.nonefound').css('display', 'none'); + $('.loading').css('display', 'block'); + $('.bounty_row').remove(); + + // filter + var uri = get_search_URI(); + + // analytics + var params = { uri: uri }; + + mixpanel.track('Refresh jobs', params); + + // order + $.get(uri, function(results) { + results = sanitizeAPIResults(results); + + if (results.length === 0) { + $('.nonefound').css('display', 'block'); + } + + document.is_painting_now = false; + document.last_bounty_rendered = 0; + document.jobs_html = []; + + for (var i = 0; i < results.length; i++) { + // setup + var result = results[i]; + var related_token_details = tokenAddressToDetails(result['token_address']); + var decimals = 18; + + if (related_token_details && related_token_details.decimals) { + decimals = related_token_details.decimals; + } + + var divisor = Math.pow(10, decimals); + + result['rounded_amount'] = Math.round(result['value_in_token'] / divisor * 100) / 100; + var is_expired = new Date(result['expires_date']) < new Date() && !result['is_open']; + + // setup args to go into template + // if (typeof web3 != 'undefined' && web3.eth.coinbase == result['bounty_owner_address']) { + // result['my_bounty'] = 'mine'; + // } else if (result['fulfiller_address'] !== '0x0000000000000000000000000000000000000000') { + // result['my_bounty'] = '' + result['status'] + ''; + // } + + result.action = result['url']; + result['title'] = result['title'] ? result['title'] : result['github_url']; + + var timeLeft = timeDifference(new Date(result['expiry_date']), new Date(), true); + + result['p'] = ((result['experience_level'] ? result['experience_level'] : 'Unknown Experience Level') + ' • '); + + // if (result['status'] === 'done') + // result['p'] += 'Done'; + // if (result['fulfillment_accepted_on']) { + // result['p'] += ' ' + timeDifference(new Date(), new Date(result['fulfillment_accepted_on']), false, 60 * 60); + // } else if (result['status'] === 'started') { + // result['p'] += 'Started'; + // result['p'] += ' ' + timeDifference(new Date(), new Date(result['fulfillment_started_on']), false, 60 * 60); + // } else if (result['status'] === 'submitted') { + // result['p'] += 'Submitted'; + // if (result['fulfillment_submitted_on']) { + // result['p'] += ' ' + timeDifference(new Date(), new Date(result['fulfillment_submitted_on']), false, 60 * 60); + // } + // } else if (result['status'] == 'cancelled') { + // result['p'] += 'Cancelled'; + // if (result['canceled_on']) { + // result['p'] += ' ' + timeDifference(new Date(), new Date(result['canceled_on']), false, 60 * 60); + // } + // } else if (is_expired) { + // var time_ago = timeDifference(new Date(), new Date(result['expiry_date']), true); + + // result['p'] += ('Expired ' + time_ago + ' ago'); + // } else { + // var opened_when = timeDifference(new Date(), new Date(result['web3_created']), true); + + // result['p'] += ('Opened ' + opened_when + ' ago, Expires in ' + timeLeft); + // } + + result['watch'] = 'Watch'; + + // render the template + var tmpl = $.templates('#result'); + var html = tmpl.render(result); + + document.jobs_html[i] = html; + } + + paint_jobs_in_viewport(0, 10); + + process_stats(results); + }).fail(function() { + _alert({message: 'got an error. please try again, or contact support@gitcoin.co'}, 'error'); + }).always(function() { + $('.loading').css('display', 'none'); + }); +}; + +window.addEventListener('load', function() { + set_sidebar_defaults(); + refreshjobs(); +}); + +var getNextDayOfWeek = function(date, dayOfWeek) { + var resultDate = new Date(date.getTime()); + + resultDate.setDate(date.getDate() + (7 + dayOfWeek - date.getDay() - 1) % 7 + 1); + return resultDate; +}; + +function getURLParams(k) { + var p = {}; + + location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(s, k, v) { + p[k] = v; + }); + return k ? p[k] : p; +} + +var resetFilters = function() { + for (var i = 0; i < sidebar_keys.length; i++) { + var key = sidebar_keys[i]; + var tag = ($('input[name=' + key + '][value]')); + + for (var j = 0; j < tag.length; j++) { + if (tag[j].value == 'any') + $('input[name=' + key + '][value=any]').prop('checked', true); + else + $('input[name=' + key + '][value=' + tag[j].value + ']').prop('checked', false); + } + } +}; + +(function() { + if (document.referrer.search('/onboard') != -1) { + $('#sidebar_container').addClass('invisible'); + $('#dashboard-title').addClass('hidden'); + $('#onboard-dashboard').removeClass('hidden'); + resetFilters(); + $('input[name=idx_status][value=open]').prop('checked', true); + $('.search-area input[type=text]').text(getURLParams('q')); + document.referrer = ''; + + $('#onboard-alert').click(function(e) { + $('.bounty_row').each(function(index) { + $(this).removeClass('hidden'); + }); + $('#onboard-dashboard').addClass('hidden'); + $('#sidebar_container').removeClass('invisible'); + $('#dashboard-title').removeClass('hidden'); + e.preventDefault(); + }); + } else { + $('#onboard-dashboard').addClass('hidden'); + $('#sidebar_container').removeClass('invisible'); + $('#dashboard-title').removeClass('hidden'); + } +})(); + +$(document).ready(function() { + + // Sort select menu + $('#sort_option').selectmenu({ + select: function(event, ui) { + refreshjobs(); + event.preventDefault(); + } + }); + + // TODO: DRY + function split(val) { + return val.split(/,\s*/); + } + + function extractLast(term) { + return split(term).pop(); + } + + technologies.forEach(function(v, k) { + $('#tech-stack-options').append('
\n' + + '' + + '' + + '
' + + '' + + '
' + + '
'); + }); + + // Handle search input clear + $('.close-icon') + .on('click', function(e) { + e.preventDefault(); + $('#keywords').val(''); + $(this).hide(); + }); + + $('#keywords') + .on('input', function() { + if ($(this).val()) { + $('.close-icon').show(); + } else { + $('.close-icon').hide(); + } + }) + // don't navigate away from the field on tab when selecting an item + .on('keydown', function(event) { + if (event.keyCode === $.ui.keyCode.TAB && $(this).autocomplete('instance').menu.active) { + event.preventDefault(); + } + }) + .autocomplete({ + minLength: 0, + source: function(request, response) { + // delegate back to autocomplete, but extract the last term + response($.ui.autocomplete.filter(document.keywords, extractLast(request.term))); + }, + focus: function() { + // prevent value inserted on focus + return false; + }, + select: function(event, ui) { + var terms = split(this.value); + var isTechStack = false; + + $('.close-icon').hide(); + + // remove the current input + terms.pop(); + + // add the selected item + terms.push(ui.item.value); + + // add placeholder to get the comma-and-space at the end + terms.push(''); + + // this.value = terms.join(', '); + this.value = ''; + + technologies.forEach(function(v, k) { + if (v.toLowerCase() === ui.item.value) { + isTechStack = true; + + $('.filter-tags').append('' + ui.item.value + '' + + ''); + + $('input[name="tech_stack"][value=' + ui.item.value + ']').prop('checked', true); + } + }); + + if (!isTechStack) { + if (localStorage['keywords']) { + localStorage['keywords'] += ',' + ui.item.value; + } else { + localStorage['keywords'] += ui.item.value; + } + + $('.filter-tags').append('' + ui.item.value + '' + + ''); + } + + return false; + } + }); + + // sidebar clear + $('.dashboard #clear').click(function(e) { + e.preventDefault(); + + for (var i = 0; i < sidebar_keys.length; i++) { + var key = sidebar_keys[i]; + var tag = ($('input[name=' + key + '][value]')); + + for (var j = 0; j < tag.length; j++) { + if (tag[j].value === 'any') + $('input[name=' + key + '][value=any]').prop('checked', true); + else + $('input[name=' + key + '][value=' + tag[j].value + ']').prop('checked', false); + } + } + + refreshjobs(); + }); + + // search bar + $('#jobs').delegate('#new_search', 'click', function(e) { + refreshjobs(); + e.preventDefault(); + }); + + $('.search-area input[type=text]').keypress(function(e) { + if (e.which == 13) { + refreshjobs(); + e.preventDefault(); + } + }); + + // sidebar filters + $('.sidebar_search input[type=radio], .sidebar_search label').change(function(e) { + refreshjobs(); + e.preventDefault(); + }); + + // sidebar filters + $('.sidebar_search input[type=checkbox], .sidebar_search label').change(function(e) { + refreshjobs(e); + e.preventDefault(); + }); + + // email subscribe functionality + $('.save_search').click(function(e) { + e.preventDefault(); + $('#save').remove(); + var url = '/sync/search_save'; + + setTimeout(function() { + $.get(url, function(newHTML) { + $(newHTML).appendTo('body').modal(); + $('#save').append(""); + $('#save_email').focus(); + }); + }, 300); + }); + + var emailSubscribe = function() { + var email = $('#save input[type=email]').val(); + var raw_data = $('#save input[type=hidden]').val(); + var is_validated = validateEmail(email); + + if (!is_validated) { + _alert({ message: gettext('Please enter a valid email address.') }, 'warning'); + } else { + var url = '/sync/search_save'; + + $.post(url, { + email: email, + raw_data: raw_data + }, function(response) { + var status = response['status']; + + if (status == 200) { + _alert({message: gettext("You're in! Keep an eye on your inbox for the next funding listing.")}, 'success'); + $.modal.close(); + } else { + _alert({message: response['msg']}, 'error'); + } + }); + } + }; + + $('body').delegate('#save input[type=email]', 'keypress', function(e) { + if (e.which == 13) { + emailSubscribe(); + e.preventDefault(); + } + }); + $('body').delegate('#save a', 'click', function(e) { + emailSubscribe(); + e.preventDefault(); + }); +}); diff --git a/app/jobs/__init__.py b/app/jobs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/jobs/admin.py b/app/jobs/admin.py new file mode 100644 index 00000000000..0f83a379e00 --- /dev/null +++ b/app/jobs/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from .models import Job + + +@admin.register(Job) +class JobAdmin(admin.ModelAdmin): + list_display = ['id', 'title', 'skills', 'is_active'] diff --git a/app/jobs/api.py b/app/jobs/api.py new file mode 100644 index 00000000000..1af4ce56e6a --- /dev/null +++ b/app/jobs/api.py @@ -0,0 +1,12 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets + +from .models import Job +from .serializers import JobSerializer + + +class JobViewSet(viewsets.ModelViewSet): + queryset = Job.objects.all() + serializer_class = JobSerializer + filter_backends = (DjangoFilterBackend, ) + filter_fields = ('job_type', ) diff --git a/app/jobs/apps.py b/app/jobs/apps.py new file mode 100644 index 00000000000..14c323ab59f --- /dev/null +++ b/app/jobs/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class JobsConfig(AppConfig): + name = 'jobs' diff --git a/app/jobs/forms.py b/app/jobs/forms.py new file mode 100644 index 00000000000..01071e5070d --- /dev/null +++ b/app/jobs/forms.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +"""Define external bounty related forms. + +Copyright (C) 2018 Gitcoin Core + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +""" +from django.forms import ModelForm + +from .models import Job + + +class JobForm(ModelForm): + """Define the Job form handling.""" + + class Meta: + """Define the JOB form metadata.""" + + model = Job + fields = [ + 'title', 'description', 'github_profile_link', 'apply_url', + 'is_active', 'skills', 'expiry_date' + ] diff --git a/app/jobs/migrations/0001_initial.py b/app/jobs/migrations/0001_initial.py new file mode 100644 index 00000000000..35f4faafcfc --- /dev/null +++ b/app/jobs/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 2.0.4 on 2018-05-05 14:14 + +import django.contrib.postgres.fields +from django.db import migrations, models +import jobs.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Job', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200, verbose_name='Title')), + ('description', models.TextField(verbose_name='Description')), + ('github_profile_link', models.URLField(blank=True, verbose_name='Github Profile Link')), + ('job_type', models.CharField(max_length=50, verbose_name='job type')), + ('apply_url', models.URLField(blank=True)), + ('is_active', models.BooleanField(default=False, verbose_name='Is this job active?')), + ('skills', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=30, verbose_name='skill'), blank=True, null=True, size=None)), + ('expiry_date', models.DateTimeField(default=jobs.models.get_expiry_time, verbose_name='Expiry Date')), + ], + options={ + 'verbose_name': 'Job', + 'verbose_name_plural': 'Jobs', + 'db_table': 'job', + }, + ), + ] diff --git a/app/jobs/migrations/0002_auto_20180505_1435.py b/app/jobs/migrations/0002_auto_20180505_1435.py new file mode 100644 index 00000000000..efa5a377489 --- /dev/null +++ b/app/jobs/migrations/0002_auto_20180505_1435.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0.4 on 2018-05-05 14:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jobs', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='location', + field=models.CharField(blank=True, max_length=50, verbose_name='Location'), + ), + migrations.AlterField( + model_name='job', + name='job_type', + field=models.CharField(choices=[('Full Time', 'Full Time'), ('Part Time', 'Part Time')], max_length=50, verbose_name='job type'), + ), + ] diff --git a/app/jobs/migrations/__init__.py b/app/jobs/migrations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/jobs/models.py b/app/jobs/models.py new file mode 100644 index 00000000000..68df77df25a --- /dev/null +++ b/app/jobs/models.py @@ -0,0 +1,58 @@ +from datetime import timedelta + +from django.db import models +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ +from django.contrib.postgres.fields import ArrayField + +# Create your models here. + +SKILL_CHOICES = ( +) + +JOB_TYPE_CHOICES = ( + ('Full Time', 'Full-Time'), + ('Part Time', 'Part-Time'), + ('Contract', 'Contract'), +) + + +def get_expiry_time(): + return timezone.now() + timedelta(days=30) + + +class Job(models.Model): + title = models.CharField( + verbose_name=_('Title'), max_length=200, null=False, blank=False + ) + description = models.TextField(verbose_name=_('Description')) + github_profile_link = models.URLField( + verbose_name=_('Github Profile Link'), null=False, blank=True + ) + location = models.CharField( + _('Location'), max_length=50, null=False, blank=True + ) + job_type = models.CharField( + _('Job Type'), max_length=50, choices=JOB_TYPE_CHOICES, null=False, + blank=False + ) + apply_url = models.URLField(null=False, blank=True) + is_active = models.BooleanField( + verbose_name=_('Is this job active?'), default=False, + null=False, blank=True + ) + skills = ArrayField( + models.CharField( + verbose_name=_('skill'), + choices=SKILL_CHOICES, max_length=30, null=False, blank=True + ), + null=True, blank=True + ) + expiry_date = models.DateTimeField( + _('Expiry Date'), null=False, blank=False, default=get_expiry_time + ) + + class Meta: + db_table = 'job' + verbose_name = _('Job') + verbose_name_plural = _('Jobs') diff --git a/app/jobs/routers.py b/app/jobs/routers.py new file mode 100644 index 00000000000..0f893a69269 --- /dev/null +++ b/app/jobs/routers.py @@ -0,0 +1,7 @@ +from rest_framework import routers + +from .api import JobViewSet + + +router = routers.DefaultRouter() +router.register(r'jobs', JobViewSet) diff --git a/app/jobs/serializers.py b/app/jobs/serializers.py new file mode 100644 index 00000000000..affcbeb3bd8 --- /dev/null +++ b/app/jobs/serializers.py @@ -0,0 +1,16 @@ + +# Third Party Stuff +from rest_framework import serializers + +from .models import Job + + +class JobSerializer(serializers.ModelSerializer): + + class Meta: + model = Job + fields = ( + 'title', 'description', 'github_profile_link', 'apply_url', + 'is_active', 'skills', 'expiry_date', 'location', + 'job_type' + ) diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html new file mode 100644 index 00000000000..00944485c0d --- /dev/null +++ b/app/jobs/templates/jobs/detail.html @@ -0,0 +1,311 @@ +{% load i18n static %} + + + + + {% include 'shared/head.html' %} + {% include 'shared/cards_pic.html' %} + + + + + + {% include 'shared/tag_manager_2.html' %} +
+ {% include 'shared/nav.html' %} +
+
+
+ {% include 'shared/current_balance.html' %} + +
+ +
+ + +
+ +
+ + + + + +
+
+
+
+
+
+
+

+ +
+
+
+
+
+
+
+ {% trans "Time left" %} + +
+
+ + + +
+
+
+ {% trans "Opened" %} + +
+
+ {% trans "Accepted" %} + +
+
+ {% trans "Experience Level" %} + +
+
+ {% trans "Issue Type" %} + +
+
+
+
+
+
+
+
+
+
+
+
+

+ +

+
+
+

+ + USD @ + + + as of + +

+
+
+
+
+
+
{% trans "Description" %}
+
+
+
+
+
+
+
{% trans "Contributors" %}
+
+
+ +
{% trans "Work Started" %}
+
    +
    +
    + +
    {% trans "Work Submitted" %}
    +
      +
      +
      + +
      {% trans "Work Accepted" %}
      +
        +
        +
        +
        +
        +
        {% trans "All Activity" %}
        +
        +
        +
        +
        {% trans "Funder" %}
        +
        +
        +
        +
        +
        +
        +
        + {% trans "Funder" %} : + +
        +
        + {% trans "Email" %} : + +
        +
        + {% trans "Gitcoin Profile" %} : + +
        +
        +
        +
        +
        +
        +
        +
        + + + + + + {% include 'shared/bottom_notification.html' %} + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' %} + {% include 'shared/rollbar.html' %} + {% include 'shared/footer.html' %} + + + + + + + + + + + + + + + diff --git a/app/jobs/templates/jobs/list.html b/app/jobs/templates/jobs/list.html new file mode 100644 index 00000000000..87b982ba764 --- /dev/null +++ b/app/jobs/templates/jobs/list.html @@ -0,0 +1,141 @@ +{% comment %} + Copyright (C) 2017 Gitcoin Core + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +{% endcomment %} +{% load i18n static %} + + + + + {% include 'shared/head.html' %} + {% include 'shared/cards.html' %} + + + + + + {% include 'shared/tag_manager_2.html' %} +
        + {% include 'shared/nav.html' %} +
        + +
        + + +
        +
        + +
        + +
        + +
        +
        +
        + + + + + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' %} + {% include 'shared/rollbar.html' %} + {% include 'shared/footer.html' %} + + + + + + + diff --git a/app/jobs/templates/jobs/sidebar_search.html b/app/jobs/templates/jobs/sidebar_search.html new file mode 100644 index 00000000000..e5c533b68f0 --- /dev/null +++ b/app/jobs/templates/jobs/sidebar_search.html @@ -0,0 +1,87 @@ +{% comment %} + Copyright (C) 2018 Gitcoin Core + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +{% endcomment %} +{% load i18n static %} + + diff --git a/app/jobs/tests.py b/app/jobs/tests.py new file mode 100644 index 00000000000..7ce503c2dd9 --- /dev/null +++ b/app/jobs/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/jobs/views.py b/app/jobs/views.py new file mode 100644 index 00000000000..72c6d595b14 --- /dev/null +++ b/app/jobs/views.py @@ -0,0 +1,22 @@ +# Third-Party imports +from django.template.response import TemplateResponse + +# gitcoin-web imports +from .models import Job + + +def list_jobs(request): + context = { + 'jobs': Job.objects.all() + } + + return TemplateResponse(request, 'jobs/list.html', context=context) + + +def job_detail(request, pk): + job = Job.objects.filter(pk=pk) + context = { + 'job': job + } + + return TemplateResponse(request, 'jobs/detail.html', context=context) From e861b0f6a93c655036fc84464e1b6eef3d7431ed Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sun, 13 May 2018 15:36:01 +0530 Subject: [PATCH 02/32] Overall styling for detail page with static data --- app/assets/v2/css/jobs.css | 173 ++++++---------- app/jobs/templates/jobs/detail.html | 300 +++++++--------------------- app/jobs/templates/jobs/list.html | 3 +- 3 files changed, 134 insertions(+), 342 deletions(-) diff --git a/app/assets/v2/css/jobs.css b/app/assets/v2/css/jobs.css index ae4f9c8cb05..06f4fa39003 100644 --- a/app/assets/v2/css/jobs.css +++ b/app/assets/v2/css/jobs.css @@ -3,153 +3,108 @@ body { background: #FFFFFF; } -.invisible { - background: #FFFFFF !important; - visibility: hidden; +h3, h4 { + text-transform: none; } -#onboard-dashboard { - background: #E8FFF2; - padding-top: 2em; - padding-bottom: 1em; - margin-bottom: 2em; +h4 { + text-align: left; } -#onboard-dashboard h2, { - font-weight: bolder; +.job-header { + padding-bottom: 6em; } -#onboard-dashboard h2, -#onboard-dashboard h3 { - text-transform: capitalize; - letter-spacing: 0; -} - -#onboard-dashboard a { - float: right; - padding-left: 25px; - padding-right: 25px; -} - -.title-row { - display: flex; - margin-top: 2em; - padding-bottom: 5px; - border-bottom: 2px solid #3E24FB; -} - -#filter { - color: #3e24fb; +#job-board-title { + text-transform: uppercase; + color: #00A55E; + text-align: center; + font-size: 3em; } -.bounty-info { - top: 4px; - text-align: right; +.company-name, #exp-lang, #job-post-duration { + color: #717171; } -#stats { - padding-left: 5px; - font-family: 'Muli', sans-serif; - font-weight: 300; - color: #7A7A7A; +#job-details { + padding-top: 3em; } -#matches { - font-family: 'Muli', sans-serif; - font-weight: 300; - padding-left: 10px; +.job-type { + color: #00A55E; + text-transform: uppercase; + flex-grow: 1; } -.title { - text-transform: capitalize; +.job-location { + flex-grow: 1; } -.bounty_row { - color: #000000; - text-decoration: none; - padding: 10px 20px 10px 4em; +.flex-grow-8 { + flex-grow: 8; } -.bounty_row .avatar-container { - text-align: right; +.job-type-loc { + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 1em 0 1em 0; } -.bounty_row img { - margin-top: 6px; - margin-right: 20px; +.job-company-exp { + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 1em 0 1em 0; } -.bounty_row .title { - margin-bottom: 2px; +.job-posted { + flex-grow: 8; } -.bounty_row:hover { - color: #000000; - text-decoration: none; - background-color: #F9F9F9; +.job-experience { + flex-grow: 1; } -.bounty-detail { - margin-top: 10px; - margin-bottom: 10px; +.job-company { + flex-grow: 1; } -.bounty-summary { - display: inline-flex; +.job-tags { + margin: 1em 0 1em 0; } -.bounty-summary img { - position: absolute; - width: 20px; - margin: 0; - top: 2px; +.tags { + display: flex; } -.bounty-summary .col-xs-1 { - margin-right: 2em; +.box { + padding: 14px; + border: 1px solid #DBDBDB; + border-radius: 3px; + margin: 0; } -.bounty-summary .github-comment { - position: absolute; - text-align: center; - color: white; - width: 20px; - top: 0px; +.btn-white { + border: 1px solid #979797; + color: #0D0764; + border-radius: 3px; + position: relative; } -.bounty-detail .info { - font-family: 'Muli', sans-serif; - font-weight: 300; - color: #666666; +.job-apply-btn { + background-color: #0D0764; + color: #FFFFFF; } -.tags { - display: flex; +.github-profile-btn { + color: #0D0764; } -@media (max-width: 768px) { - - #onboard-dashboard .col-12 { - padding-left: 3em; - padding-right: 3em; - } - - #filter { - padding-left: 20px; - } - - .bounty_row { - padding-left: 1em; - padding-right: 2em; - } - - .bounty-info { - top: 10px; - font-size: 10px; - text-align: left; - } - - #stats { - font-size: 10px; - } +.tag.keyword { + min-width: 0; + font-size: 11px; + padding: 5px 10px; + background-color: #F2F6F9; + color: #6184AC; } diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html index 00944485c0d..c2747e033f5 100644 --- a/app/jobs/templates/jobs/detail.html +++ b/app/jobs/templates/jobs/detail.html @@ -5,7 +5,7 @@ {% include 'shared/head.html' %} {% include 'shared/cards_pic.html' %} - + @@ -23,22 +23,6 @@
        - - -
        - -
        -
        -
        -
        -
        -
        -
        -
        -

        - -
        -
        -
        -
        -
        -
        -
        - {% trans "Time left" %} - -
        -
        - - - -
        -
        -
        - {% trans "Opened" %} - -
        -
        - {% trans "Accepted" %} - -
        -
        - {% trans "Experience Level" %} - -
        -
        - {% trans "Issue Type" %} - -
        -
        -
        +
        +

        Blockchain/Smart Contract Engineer

        +
        +
        FULL-TIME
        +
        Singapore
        +
        -
        -
        -
        -
        -
        -
        -
        -
        -

        - -

        -
        -
        -

        - - USD @ - - - as of - -

        -
        -
        -
        -
        -
        -
        {% trans "Description" %}
        -
        -
        -
        -
        -
        -
        -
        {% trans "Contributors" %}
        -
        -
        - -
        {% trans "Work Started" %}
        -
          -
          -
          - -
          {% trans "Work Submitted" %}
          -
            -
            -
            - -
            {% trans "Work Accepted" %}
            -
              -
              -
              -
              -
              -
              {% trans "All Activity" %}
              -
              -
              -
              -
              {% trans "Funder" %}
              -
              -
              -
              -
              -
              -
              -
              - {% trans "Funder" %} : - -
              -
              - {% trans "Email" %} : - -
              -
              - {% trans "Gitcoin Profile" %} : - -
              -
              -
              -
              +
              +
              Company: Truffle
              +
              Experience: Java
              +
              Posted: 2 days ago
              -
              -
              -
              + + Apply on Company Site + + + Github Profile + +

              Description

              +
              + We're looking for an experienced blockchain engineer to lead the development efforts of our Solidity smart contracts. - + Our mission is to allow buyers and sellers to meet and transact in a completely distributed way (from car, housing, bike, and other rentals to task-based services like freelance engineering, design, marketing and more). - +
              {% include 'shared/bottom_notification.html' %} {% include 'shared/analytics.html' %} @@ -303,7 +139,7 @@
              {% trans "Funder" %}
              - + diff --git a/app/jobs/templates/jobs/list.html b/app/jobs/templates/jobs/list.html index 87b982ba764..59f72164809 100644 --- a/app/jobs/templates/jobs/list.html +++ b/app/jobs/templates/jobs/list.html @@ -28,8 +28,9 @@ {% include 'shared/tag_manager_2.html' %} -
              +
              {% include 'shared/nav.html' %} +
              Job Board
              From bf64a0e97a3f630bfc31fbede1482819e6f87d3f Mon Sep 17 00:00:00 2001 From: Mark Beacom Date: Mon, 14 May 2018 15:23:24 -0400 Subject: [PATCH 03/32] Fix eslint, stylelint, and isort --- app/app/urls.py | 2 +- app/assets/v2/css/jobs.css | 26 +++++++++++++------------- app/assets/v2/js/pages/jobs.js | 5 +++-- app/jobs/models.py | 2 +- app/jobs/routers.py | 1 - 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/app/urls.py b/app/app/urls.py index af8e87f5c35..56c045efd1c 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -34,13 +34,13 @@ import external_bounties.views import faucet.views import gitcoinbot.views +import jobs.views import linkshortener.views import marketing.views import marketing.webhookviews import retail.emails import retail.views import tdi.views -import jobs.views from dashboard.router import router as dbrouter from external_bounties.router import router as ebrouter from jobs.routers import router as job_router diff --git a/app/assets/v2/css/jobs.css b/app/assets/v2/css/jobs.css index 06f4fa39003..75ec5ed5f6f 100644 --- a/app/assets/v2/css/jobs.css +++ b/app/assets/v2/css/jobs.css @@ -79,17 +79,17 @@ h4 { } .box { - padding: 14px; - border: 1px solid #DBDBDB; - border-radius: 3px; - margin: 0; + padding: 14px; + border: 1px solid #DBDBDB; + border-radius: 3px; + margin: 0; } .btn-white { - border: 1px solid #979797; - color: #0D0764; - border-radius: 3px; - position: relative; + border: 1px solid #979797; + color: #0D0764; + border-radius: 3px; + position: relative; } .job-apply-btn { @@ -102,9 +102,9 @@ h4 { } .tag.keyword { - min-width: 0; - font-size: 11px; - padding: 5px 10px; - background-color: #F2F6F9; - color: #6184AC; + min-width: 0; + font-size: 11px; + padding: 5px 10px; + background-color: #F2F6F9; + color: #6184AC; } diff --git a/app/assets/v2/js/pages/jobs.js b/app/assets/v2/js/pages/jobs.js index 1a44e8334fe..ec54f1969f1 100644 --- a/app/assets/v2/js/pages/jobs.js +++ b/app/assets/v2/js/pages/jobs.js @@ -115,7 +115,8 @@ var set_filter_header = function() { var idxStatusEl = $('input[name=idx_status]:checked'); var filter_status = idxStatusEl.attr('val-ui') ? idxStatusEl.attr('val-ui') : 'All'; // TODO: See what all filters are to be displayed from designs - $('#filter').html("All"); + + $('#filter').html('All'); }; var toggleAny = function(event) { @@ -293,7 +294,7 @@ var process_stats = function(results) { var matchesEl = $('#matches'); var - listingInfoEl = $('#listing-info'); + listingInfoEl = $('#listing-info'); switch (num) { case 0: diff --git a/app/jobs/models.py b/app/jobs/models.py index 68df77df25a..0fd137909d9 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -1,9 +1,9 @@ from datetime import timedelta +from django.contrib.postgres.fields import ArrayField from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.contrib.postgres.fields import ArrayField # Create your models here. diff --git a/app/jobs/routers.py b/app/jobs/routers.py index 0f893a69269..50253f9b2d8 100644 --- a/app/jobs/routers.py +++ b/app/jobs/routers.py @@ -2,6 +2,5 @@ from .api import JobViewSet - router = routers.DefaultRouter() router.register(r'jobs', JobViewSet) From 11af0de623025c5e148ae19bb61e2f28523ef6c1 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sat, 19 May 2018 16:49:54 +0530 Subject: [PATCH 04/32] WIP job detail view --- app/app/urls.py | 4 +- app/assets/v2/css/jobs.css | 12 +- app/assets/v2/js/pages/job_details.js | 804 ++++++++++++++++++++++++++ app/assets/v2/js/pages/jobs.js | 50 +- app/jobs/models.py | 27 + app/jobs/serializers.py | 4 +- app/jobs/templates/jobs/detail.html | 9 +- app/jobs/templates/jobs/list.html | 44 +- app/jobs/views.py | 10 +- 9 files changed, 873 insertions(+), 91 deletions(-) create mode 100644 app/assets/v2/js/pages/job_details.js diff --git a/app/app/urls.py b/app/app/urls.py index 56c045efd1c..1f228d773d7 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -258,8 +258,8 @@ # Job urls goes below - path('jobs', jobs.views.list_jobs, name='jobs'), - path('jobs//', jobs.views.job_detail, name='jobs'), + path('jobs', jobs.views.list_jobs, name='job-list-template'), + path('jobs//', jobs.views.job_detail, name='job-detail-template'), ] if settings.ENABLE_SILK: diff --git a/app/assets/v2/css/jobs.css b/app/assets/v2/css/jobs.css index 75ec5ed5f6f..3e32ff18c8d 100644 --- a/app/assets/v2/css/jobs.css +++ b/app/assets/v2/css/jobs.css @@ -15,6 +15,10 @@ h4 { padding-bottom: 6em; } +.job-row { + padding: 1em; +} + #job-board-title { text-transform: uppercase; color: #00A55E; @@ -26,8 +30,9 @@ h4 { color: #717171; } -#job-details { - padding-top: 3em; +.job-details { + padding-top: 1em; + display: flex; } .job-type { @@ -95,6 +100,9 @@ h4 { .job-apply-btn { background-color: #0D0764; color: #FFFFFF; + padding-left: 2em; + padding-right:2em; + margin-right: 1em; } .github-profile-btn { diff --git a/app/assets/v2/js/pages/job_details.js b/app/assets/v2/js/pages/job_details.js new file mode 100644 index 00000000000..442f4a185e5 --- /dev/null +++ b/app/assets/v2/js/pages/job_details.js @@ -0,0 +1,804 @@ +/* eslint block-scoped-var: "warn" */ +/* eslint no-redeclare: "warn" */ + + +var _truthy = function(val) { + if (!val || val == '0x0000000000000000000000000000000000000000') { + return false; + } + return true; +}; +var address_ize = function(key, val, result) { + if (!_truthy(val)) { + return [ null, null ]; + } + return [ key, '' + val + '' ]; +}; +var gitcoin_ize = function(key, val, result) { + if (!_truthy(val)) { + return [ null, null ]; + } + return [ key, '@' + val.replace('@', '') + '' ]; +}; +var email_ize = function(key, val, result) { + if (!_truthy(val)) { + return [ null, null ]; + } + return [ key, '' + val + '' ]; +}; +var hide_if_empty = function(key, val, result) { + if (!_truthy(val)) { + return [ null, null ]; + } + return [ key, val ]; +}; +var unknown_if_empty = function(key, val, result) { + if (!_truthy(val)) { + return [ key, 'Unknown' ]; + } + return [ key, val ]; +}; +var link_ize = function(key, val, result) { + if (!_truthy(val)) { + return [ null, null ]; + } + return [ key, '' + val + '' ]; +}; + +// rows in the 'about' page +var rows = [ + 'avatar_url', + 'issuer_avatar_url', + 'title', + 'github_url', + 'value_in_token', + 'value_in_eth', + 'value_in_usdt', + 'token_value_in_usdt', + 'token_value_time_peg', + 'web3_created', + 'status', + 'bounty_owner_address', + 'bounty_owner_email', + 'issue_description', + 'bounty_owner_github_username', + 'fulfillments', + 'network', + 'experience_level', + 'project_length', + 'bounty_type', + 'expires_date', + 'bounty_owner_name', + 'issue_keywords', + 'started_owners_username', + 'submitted_owners_username', + 'fulfilled_owners_username', + 'fulfillment_accepted_on' +]; +var heads = { + 'avatar_url': gettext('Issue'), + 'value_in_token': gettext('Issue Funding Info'), + 'bounty_owner_address': gettext('Funder'), + 'fulfiller_address': gettext('Submitter'), + 'experience_level': gettext('Meta') +}; +var callbacks = { + 'github_url': link_ize, + 'value_in_token': function(key, val, result) { + return [ 'amount', Math.round((parseInt(val) / Math.pow(10, document.decimals)) * 1000) / 1000 + ' ' + result['token_name'] ]; + }, + 'avatar_url': function(key, val, result) { + return [ 'avatar', '' ]; + }, + 'issuer_avatar_url': function(key, val, result) { + var username = result['bounty_owner_github_username'] ? result['bounty_owner_github_username'] : 'Self'; + + return [ 'issuer_avatar_url', '' ]; + }, + 'status': function(key, val, result) { + var ui_status = val; + + if (ui_status == 'open') { + ui_status = '' + gettext('OPEN ISSUE') + ''; + } + if (ui_status == 'started') { + ui_status = '' + gettext('work started') + ''; + } + if (ui_status == 'submitted') { + ui_status = '' + gettext('work submitted') + ''; + } + if (ui_status == 'done') { + ui_status = '' + gettext('done') + ''; + } + if (ui_status == 'cancelled') { + ui_status = '' + gettext('cancelled') + ''; + } + return [ 'status', ui_status ]; + }, + 'issue_description': function(key, val, result) { + var converter = new showdown.Converter(); + + val = val.replace(/script/ig, 'scr_i_pt'); + var ui_body = val; + + ui_body = converter.makeHtml(ui_body); + + return [ 'issue_description', ui_body ]; + }, + 'bounty_owner_address': address_ize, + 'bounty_owner_email': email_ize, + 'experience_level': unknown_if_empty, + 'project_length': unknown_if_empty, + 'bounty_type': unknown_if_empty, + 'bounty_owner_github_username': gitcoin_ize, + 'bounty_owner_name': function(key, val, result) { + return [ 'bounty_owner_name', result.metadata.fullName ]; + }, + 'issue_keywords': function(key, val, result) { + if (!result.metadata.issueKeywords || result.metadata.issueKeywords.length == 0) + return [ 'issue_keywords', null ]; + + var keywords = result.metadata.issueKeywords.split(','); + var tags = []; + + keywords.forEach(function(keyword) { + tags.push('
              ' + keyword + '
              '); + }); + return [ 'issue_keywords', tags ]; + }, + 'value_in_eth': function(key, val, result) { + if (result['token_name'] == 'ETH' || val === null) { + return [ null, null ]; + } + return [ 'Amount (ETH)', Math.round((parseInt(val) / Math.pow(10, 18)) * 1000) / 1000 ]; + }, + 'value_in_usdt': function(key, val, result) { + if (val === null) { + return [ null, null ]; + } + var rates_estimate = get_rates_estimate(val); + + $('#value_in_usdt_wrapper').attr('title', '
              ' + rates_estimate + '
              '); + + return [ 'Amount_usd', val ]; + }, + 'fulfillment_accepted_on': function(key, val, result) { + if (val === null || typeof val == 'undefined') { + $('#fulfillment_accepted_on_wrapper').addClass('hidden'); + return [ null, null ]; + } + var timePeg = timeDifference(new Date(), new Date(val), false, 60 * 60); + + return [ 'fulfillment_accepted_on', timePeg ]; + }, + 'network': function(key, val, result) { + if (val == 'mainnet') { + $('#network').addClass('hidden'); + return [ null, null ]; + } + var warning = 'WARNING: this is a ' + val + ' network bounty, and is NOT real money. To see mainnet bounties, go to the bounty explorer and search for mainnet bounties. '; + + return [ 'network', warning ]; + }, + 'token_value_time_peg': function(key, val, result) { + if (val === null || typeof val == 'undefined') { + $('#token_value_time_peg_wrapper').addClass('hidden'); + return [ null, null ]; + } + var timePeg = timeDifference(new Date(), new Date(val), false, 60 * 60); + + return [ 'token_value_time_peg', timePeg ]; + }, + 'token_value_in_usdt': function(key, val, result) { + if (val === null || typeof val == 'undefined') { + $('#value_in_usdt_wrapper').addClass('hidden'); + return [ null, null ]; + } + return [ 'Token_amount_usd', '$' + val + '/' + result['token_name'] ]; + }, + 'web3_created': function(key, val, result) { + return [ 'updated', timeDifference(new Date(result['now']), new Date(result['web3_created'])) ]; + }, + 'expires_date': function(key, val, result) { + var label = 'expires'; + + expires_date = new Date(val); + now = new Date(result['now']); + + var expiringInPercentage = 100 * ( + (now.getTime() - new Date(result['web3_created']).getTime()) / + (expires_date.getTime() - new Date(result['web3_created']).getTime())); + + if (expiringInPercentage > 100) { + expiringInPercentage = 100; + } + + $('.progress').css('width', expiringInPercentage + '%'); + var response = timeDifference(now, expires_date).split(' '); + + if (expires_date < new Date()) { + label = 'expired'; + if (result['is_open']) { + $('.timeleft').text('Expired'); + $('.progress-bar').addClass('expired'); + response = '' + response.join(' ') + ''; + } else { + $('#timer').hide(); + } + } else if (result['status'] === 'done' || result['status'] === 'cancelled') { + $('#timer').hide(); + } else { + response.shift(); + response = response.join(' '); + } + return [ label, response ]; + }, + 'started_owners_username': function(key, val, result) { + var started = []; + + if (result.interested) { + var interested = result.interested; + + interested.forEach(function(_interested, position) { + var name = (position == interested.length - 1) ? + _interested.profile.handle : _interested.profile.handle.concat(','); + + started.push(profileHtml(_interested.profile.handle, name)); + }); + if (started.length == 0) { + started.push(''); + } + } + return [ 'started_owners_username', started ]; + }, + 'submitted_owners_username': function(key, val, result) { + var accepted = []; + + if (result.fulfillments) { + var submitted = result.fulfillments; + + submitted.forEach(function(_submitted, position) { + var name = (position == submitted.length - 1) ? + _submitted.fulfiller_github_username : _submitted.fulfiller_github_username.concat(','); + + accepted.push(profileHtml(_submitted.fulfiller_github_username, name)); + }); + if (accepted.length == 0) { + accepted.push(''); + } + } + return [ 'submitted_owners_username', accepted ]; + }, + 'fulfilled_owners_username': function(key, val, result) { + var accepted = []; + var accepted_fufillments = []; + + if (result.fulfillments) { + var fulfillments = result.fulfillments; + + fulfillments.forEach(function(fufillment) { + if (fufillment.accepted == true) + accepted_fufillments.push(fufillment.fulfiller_github_username); + }); + if (accepted_fufillments.length == 0) { + accepted.push(''); + } else { + accepted_fufillments.forEach((github_username, position) => { + var name = (position == accepted_fufillments.length - 1) ? + github_username : github_username.concat(','); + + accepted.push(profileHtml(github_username, name)); + }); + } + } + return [ 'fulfilled_owners_username', accepted ]; + } +}; + +var isBountyOwner = function(result) { + var bountyAddress = result['bounty_owner_address']; + + return (typeof web3 != 'undefined' && (web3.eth.coinbase == bountyAddress)); +}; + +var update_title = function() { + document.original_title_text = $('title').text(); + setInterval(function() { + if (document.prepend_title == '(...)') { + document.prepend_title = '(*..)'; + } else if (document.prepend_title == '(*..)') { + document.prepend_title = '(.*.)'; + } else if (document.prepend_title == '(.*.)') { + document.prepend_title = '(..*)'; + } else { + document.prepend_title = '(...)'; + } + $('title').text(document.prepend_title + ' ' + document.original_title_text); + }, 2000); +}; + +var showWarningMessage = function(txid) { + + update_title(); + $('.interior .body').addClass('loading'); + + if (typeof txid != 'undefined' && txid.indexOf('0x') != -1) { + clearInterval(interval); + var link_url = etherscan_tx_url(txid); + + $('#transaction_url').attr('href', link_url); + } + + $('.left-rails').hide(); + $('#bounty_details').hide(); + $('#bounty_detail').hide(); + + $('.transaction-status').show(); + $('.waiting_room_entertainment').show(); + + var radioButtons = $('.sidebar_search input'); + + for (var i = radioButtons.length - 1; i >= 0; i--) { + radioButtons[i].disabled = true; + } + + var secondsBetweenQuoteChanges = 30; + + waitingRoomEntertainment(); + var interval = setInterval(waitingRoomEntertainment, secondsBetweenQuoteChanges * 1000); +}; + +var wait_for_tx_to_mine_and_then_ping_server = function() { + console.log('checking for updates'); + if (typeof document.pendingIssueMetadata != 'undefined') { + var txid = document.pendingIssueMetadata['txid']; + + console.log('waiting for web3 to be available'); + callFunctionWhenweb3Available(function() { + console.log('waiting for tx to be mined'); + callFunctionWhenTransactionMined(txid, function() { + console.log('tx mined'); + var data = { + url: document.issueURL, + txid: txid, + network: document.web3network + }; + var error = function(response) { + // refresh upon error + document.location.href = document.location.href; + }; + var success = function(response) { + if (response.status == '200') { + console.log('success from sync/web', response); + + // clear local data + localStorage[document.issueURL] = ''; + if (response['url']) { + document.location.href = response['url']; + } else { + document.location.href = document.location.href; + } + } else { + console.log('error from sync/web', response); + error(response); + } + }; + + console.log('syncing gitcoin with web3'); + var uri = '/sync/web3/'; + + $.ajax({ + type: 'POST', + url: uri, + data: data, + success: success, + error: error, + dataType: 'json' + }); + }); + }); + } +}; + +var attach_work_actions = function() { + $('body').delegate('a[href="/interested"], a[href="/uninterested"]', 'click', function(e) { + e.preventDefault(); + if ($(this).attr('href') == '/interested') { + show_interest_modal.call(this); + } else if (confirm(gettext('Are you sure you want to remove your interest?'))) { + $(this).attr('href', '/interested'); + $(this).find('span').text('Start Work'); + remove_interest(document.result['pk']); + } + + }); +}; + +var show_interest_modal = function() { + var self = this; + + setTimeout(function() { + $.get('/interest/modal', function(newHTML) { + var modal = $(newHTML).appendTo('body').modal({ + modalClass: 'modal add-interest-modal' + }); + + modal.on('submit', function(event) { + event.preventDefault(); + + var has_question = event.target[0].value; + var issue_message = event.target[2].value; + + var agree_precedence = event.target[3].checked; + var agree_not_to_abandon = event.target[4].checked; + + if (!agree_precedence) { + alert('You must agree to the precedence clause.'); + return false; + } + if (!agree_not_to_abandon) { + alert('You must agree not to keep the fulfiller updated on your progress.'); + return false; + } + + $(self).attr('href', '/uninterested'); + $(self).find('span').text(gettext('Stop Work')); + add_interest(document.result['pk'], { + has_question, + issue_message + }); + $.modal.close(); + }); + }); + }); +}; + + +var build_detail_page = function(result) { + + // setup + var decimals = 18; + var related_token_details = tokenAddressToDetails(result['token_address']); + + if (related_token_details && related_token_details.decimals) { + decimals = related_token_details.decimals; + } + document.decimals = decimals; + $('#bounty_details').css('display', 'inline'); + + // title + result['title'] = result['title'] ? result['title'] : result['github_url']; + $('.title').html(gettext('Funded Issue Details: ') + result['title']); + + // insert table onto page + for (var j = 0; j < rows.length; j++) { + var key = rows[j]; + var head = null; + var val = result[key]; + + if (heads[key]) { + head = heads[key]; + } + if (callbacks[key]) { + _result = callbacks[key](key, val, result); + val = _result[1]; + } + var _entry = { + 'head': head, + 'key': key, + 'val': val + }; + var id = '#' + key; + + if ($(id).length) { + $(id).html(val); + } + } + +}; + +var do_actions = function(result) { + // helper vars + var is_legacy = result['web3_type'] == 'legacy_gitcoin'; + var is_date_expired = (new Date(result['now']) > new Date(result['expires_date'])); + var is_status_expired = result['status'] == 'expired'; + var is_status_done = result['status'] == 'done'; + var is_status_cancelled = result['status'] == 'cancelled'; + var can_submit_after_expiration_date = result['can_submit_after_expiration_date']; + var is_still_on_happy_path = result['status'] == 'open' || result['status'] == 'started' || result['status'] == 'submitted' || (can_submit_after_expiration_date && result['status'] == 'expired'); + + // Find interest information + pull_interest_list(result['pk'], function(is_interested) { + + // which actions should we show? + var show_start_stop_work = is_still_on_happy_path; + var show_github_link = result['github_url'].substring(0, 4) == 'http'; + var show_submit_work = true; + var show_kill_bounty = !is_status_done && !is_status_expired && !is_status_cancelled; + var show_increase_bounty = !is_status_done && !is_status_expired && !is_status_cancelled; + var kill_bounty_enabled = isBountyOwner(result); + var submit_work_enabled = !isBountyOwner(result); + var start_stop_work_enabled = !isBountyOwner(result); + var increase_bounty_enabled = isBountyOwner(result); + var show_accept_submission = isBountyOwner(result) && !is_status_expired && !is_status_done && !is_status_expired; + + if (is_legacy) { + show_start_stop_work = false; + show_github_link = true; + show_submit_work = false; + show_kill_bounty = false; + show_accept_submission = false; + } + + // actions + var actions = []; + + if (show_submit_work) { + var enabled = submit_work_enabled; + var _entry = { + enabled: enabled, + href: result['action_urls']['fulfill'], + text: gettext('Submit Work'), + parent: 'right_actions', + title: gettext('Submit work for the funder to review'), + work_started: is_interested, + id: 'submit' + }; + + actions.push(_entry); + } + + if (show_start_stop_work) { + var enabled = start_stop_work_enabled; + var interest_entry = { + enabled: enabled, + href: is_interested ? '/uninterested' : '/interested', + text: is_interested ? gettext('Stop Work') : gettext('Start Work'), + parent: 'right_actions', + title: is_interested ? gettext('Notify the funder that you will not be working on this project') : gettext('Notify the funder that you would like to take on this project'), + color: is_interested ? 'white' : '', + id: 'interest' + }; + + actions.push(interest_entry); + } + + if (show_kill_bounty) { + var enabled = kill_bounty_enabled; + var _entry = { + enabled: enabled, + href: result['action_urls']['cancel'], + text: gettext('Cancel Bounty'), + parent: 'right_actions', + title: gettext('Cancel bounty and reclaim funds for this issue') + }; + + actions.push(_entry); + } + + var pending_acceptance = result.fulfillments.filter(fulfillment => fulfillment.accepted == false).length; + + if (show_accept_submission && pending_acceptance > 0) { + var enabled = show_accept_submission; + var _entry = { + enabled: enabled, + href: result['action_urls']['accept'], + text: gettext('Accept Submission'), + title: gettext('This will payout the bounty to the submitter.'), + parent: 'right_actions', + pending_acceptance: pending_acceptance + }; + + actions.push(_entry); + } + + if (show_increase_bounty) { + var enabled = increase_bounty_enabled; + var _entry = { + enabled: enabled, + href: result['action_urls']['increase'], + text: gettext('Add Contribution'), + parent: 'right_actions', + title: gettext('Increase the funding for this issue'), + color: 'white' + }; + + actions.push(_entry); + } + + if (show_github_link) { + var github_url = result['github_url']; + // hack to get around the renamed repo for piper's work. can't change the data layer since blockchain is immutable + + github_url = github_url.replace('pipermerriam/web3.py', 'ethereum/web3.py'); + github_url = github_url.replace('ethereum/browser-solidity', 'ethereum/remix-ide'); + var github_tooltip = gettext('View issue details and comments on Github'); + + var _entry = { + enabled: true, + href: github_url, + text: gettext('View On Github'), + parent: 'right_actions', + title: gettext('View issue details and comments on Github'), + comments: result['github_comments'], + color: 'white' + }; + + actions.push(_entry); + } + + render_actions(actions); + }); +}; + +var render_actions = function(actions) { + for (var l = 0; l < actions.length; l++) { + var target = actions[l]['parent']; + var tmpl = $.templates('#action'); + var html = tmpl.render(actions[l]); + + $('#' + target).append(html); + } +}; + +var pull_bounty_from_api = function() { + var uri = '/actions/api/v0.1/bounties/?github_url=' + document.issueURL; + + if (typeof document.issueNetwork != 'undefined') { + uri = uri + '&network=' + document.issueNetwork; + } + if (typeof document.issue_stdbounties_id != 'undefined') { + uri = uri + '&standard_bounties_id=' + document.issue_stdbounties_id; + } + $.get(uri, function(results) { + results = sanitizeAPIResults(results); + var nonefound = true; + // potentially make this a lot faster by only pulling the specific issue required + + for (var i = 0; i < results.length; i++) { + var result = results[i]; + // if the result from the database matches the one in question. + + if (normalizeURL(result['github_url']) == normalizeURL(document.issueURL)) { + nonefound = false; + + build_detail_page(result); + + do_actions(result); + + render_activity(result); + + document.result = result; + return; + } + } + if (nonefound) { + $('#primary_view').css('display', 'none'); + // is there a pending issue or not? + $('.nonefound').css('display', 'block'); + } + }).fail(function() { + _alert({message: gettext('got an error. please try again, or contact support@gitcoin.co')}, 'error'); + $('#primary_view').css('display', 'none'); + }).always(function() { + $('.loading').css('display', 'none'); + }); +}; + + +var render_activity = function(result) { + var activities = []; + + if (result.fulfillments) { + result.fulfillments.forEach(function(fulfillment) { + var link = fulfillment['fulfiller_github_url'] ? " [View Work]" : ''; + + if (fulfillment.accepted == true) { + activities.push({ + name: fulfillment.fulfiller_github_username, + address: fulfillment.fulfiller_address, + email: fulfillment.fulfiller_email, + fulfillment_id: fulfillment.fulfillment_id, + text: gettext('Work Accepted') + link, + age: timeDifference(new Date(result['now']), new Date(fulfillment.accepted_on)), + status: 'accepted' + }); + } + activities.push({ + name: fulfillment.fulfiller_github_username, + text: gettext('Work Submitted') + link, + created_on: fulfillment.created_on, + age: timeDifference(new Date(result['now']), new Date(fulfillment.created_on)), + status: 'submitted' + }); + }); + } + + if (result.interested) { + result.interested.forEach(function(_interested) { + activities.push({ + profileId: _interested.profile.id, + name: _interested.profile.handle, + text: gettext('Work Started'), + created_on: _interested.created, + age: timeDifference(new Date(result['now']), new Date(_interested.created)), + status: 'started', + uninterest_possible: isBountyOwner(result) || document.isStaff + }); + }); + } + + activities = activities.slice().sort(function(a, b) { + return a['created_on'] < b['created_on'] ? -1 : 1; + }).reverse(); + + var html = '

              ' + gettext('There\'s no activity yet!') + '

              '; + + if (activities.length > 0) { + var template = $.templates('#activity_template'); + + html = template.render(activities); + } + $('#activities').html(html); + + activities.filter(function(activity) { + return activity.uninterest_possible; + }).forEach(function(activity) { + $('#remove-' + activity.name).click(() => { + uninterested(result.pk, activity.profileId); + return false; + }); + }); + +}; + +var main = function() { + setTimeout(function() { + // setup + attach_work_actions(); + + // pull issue URL + if (typeof document.issueURL == 'undefined') { + document.issueURL = getParam('url'); + } + $('#submitsolicitation a').attr('href', '/funding/new/?source=' + document.issueURL); + + // if theres a pending submission for this issue, show the warning message + // if not, pull the data from the API + var isPending = false; + + if (localStorage[document.issueURL]) { + // validate pending issue metadata + document.pendingIssueMetadata = JSON.parse(localStorage[document.issueURL]); + var is_metadata_valid = typeof document.pendingIssueMetadata != 'undefined' && document.pendingIssueMetadata !== null && typeof document.pendingIssueMetadata['timestamp'] != 'undefined'; + + if (is_metadata_valid) { + // validate that the pending tx is within the last little while + var then = parseInt(document.pendingIssueMetadata['timestamp']); + var now = timestamp(); + var acceptableTimeDeltaSeconds = 60 * 60; // 1 hour + var isWithinAcceptableTimeRange = (now - then) < acceptableTimeDeltaSeconds; + + if (isWithinAcceptableTimeRange) { + // update from web3 + var txid = document.pendingIssueMetadata['txid']; + + showWarningMessage(txid); + wait_for_tx_to_mine_and_then_ping_server(); + isPending = true; + } + } + } + // show the actual bounty page + if (!isPending) { + pull_bounty_from_api(); + } + + }, 100); +}; + + +window.addEventListener('load', function() { + main(); +}); diff --git a/app/assets/v2/js/pages/jobs.js b/app/assets/v2/js/pages/jobs.js index ec54f1969f1..d106ffc9e0b 100644 --- a/app/assets/v2/js/pages/jobs.js +++ b/app/assets/v2/js/pages/jobs.js @@ -331,7 +331,7 @@ var paint_jobs_in_viewport = function(start, max) { $('#jobs').append(html); } - $('div.bounty_row.result').each(function() { + $('div.job-row.result').each(function() { var href = $(this).attr('href'); if (typeof $(this).changeElementType !== 'undefined') { @@ -343,7 +343,7 @@ var paint_jobs_in_viewport = function(start, max) { document.is_painting_now = false; if (document.referrer.search('/onboard') != -1) { - $('.bounty_row').each(function(index) { + $('.job-row').each(function(index) { if (index > 2) $(this).addClass('hidden'); }); @@ -355,7 +355,7 @@ var trigger_scroll = debounce(function() { return; } var scrollPos = $(document).scrollTop(); - var last_active_bounty = $('.bounty_row.result:last-child'); + var last_active_bounty = $('.job-row.result:last-child'); if (last_active_bounty.length == 0) { return; @@ -382,7 +382,7 @@ var refreshjobs = function(event) { $('.nonefound').css('display', 'none'); $('.loading').css('display', 'block'); - $('.bounty_row').remove(); + $('.job-row').remove(); // filter var uri = get_search_URI(); @@ -425,40 +425,15 @@ var refreshjobs = function(event) { // } else if (result['fulfiller_address'] !== '0x0000000000000000000000000000000000000000') { // result['my_bounty'] = '' + result['status'] + ''; // } - + console.log(result); result.action = result['url']; result['title'] = result['title'] ? result['title'] : result['github_url']; var timeLeft = timeDifference(new Date(result['expiry_date']), new Date(), true); - result['p'] = ((result['experience_level'] ? result['experience_level'] : 'Unknown Experience Level') + ' • '); - - // if (result['status'] === 'done') - // result['p'] += 'Done'; - // if (result['fulfillment_accepted_on']) { - // result['p'] += ' ' + timeDifference(new Date(), new Date(result['fulfillment_accepted_on']), false, 60 * 60); - // } else if (result['status'] === 'started') { - // result['p'] += 'Started'; - // result['p'] += ' ' + timeDifference(new Date(), new Date(result['fulfillment_started_on']), false, 60 * 60); - // } else if (result['status'] === 'submitted') { - // result['p'] += 'Submitted'; - // if (result['fulfillment_submitted_on']) { - // result['p'] += ' ' + timeDifference(new Date(), new Date(result['fulfillment_submitted_on']), false, 60 * 60); - // } - // } else if (result['status'] == 'cancelled') { - // result['p'] += 'Cancelled'; - // if (result['canceled_on']) { - // result['p'] += ' ' + timeDifference(new Date(), new Date(result['canceled_on']), false, 60 * 60); - // } - // } else if (is_expired) { - // var time_ago = timeDifference(new Date(), new Date(result['expiry_date']), true); - - // result['p'] += ('Expired ' + time_ago + ' ago'); - // } else { - // var opened_when = timeDifference(new Date(), new Date(result['web3_created']), true); - - // result['p'] += ('Opened ' + opened_when + ' ago, Expires in ' + timeLeft); - // } + result['job_company'] = ((result['company'] ? result['company'] : 'Company Hidden') + ' • '); + + result['job_skill'] += result['skills'] ? result['skills'] : '' result['watch'] = 'Watch'; @@ -504,13 +479,6 @@ var resetFilters = function() { for (var i = 0; i < sidebar_keys.length; i++) { var key = sidebar_keys[i]; var tag = ($('input[name=' + key + '][value]')); - - for (var j = 0; j < tag.length; j++) { - if (tag[j].value == 'any') - $('input[name=' + key + '][value=any]').prop('checked', true); - else - $('input[name=' + key + '][value=' + tag[j].value + ']').prop('checked', false); - } } }; @@ -525,7 +493,7 @@ var resetFilters = function() { document.referrer = ''; $('#onboard-alert').click(function(e) { - $('.bounty_row').each(function(index) { + $('.job-row').each(function(index) { $(this).removeClass('hidden'); }); $('#onboard-dashboard').addClass('hidden'); diff --git a/app/jobs/models.py b/app/jobs/models.py index 0fd137909d9..8210eac839d 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -1,6 +1,7 @@ from datetime import timedelta from django.contrib.postgres.fields import ArrayField +from django.conf import settings from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -52,6 +53,32 @@ class Job(models.Model): _('Expiry Date'), null=False, blank=False, default=get_expiry_time ) + def get_absolute_url(self): + """Get the absolute URL for the Job. + + Returns: + str: The absolute URL for the Job. + + """ + return settings.BASE_URL + self.get_relative_url(preceding_slash=False) + + def get_relative_url(self, preceding_slash=True): + """Get the relative URL for the Job. + + Attributes: + preceding_slash (bool): Whether or not to include a preceding slash. + + Returns: + str: The relative URL for the Job. + + """ + job_id = self.id + return f"{'/' if preceding_slash else ''}jobs/{job_id}/" + + @property + def url(self): + return self.get_absolute_url() + class Meta: db_table = 'job' verbose_name = _('Job') diff --git a/app/jobs/serializers.py b/app/jobs/serializers.py index affcbeb3bd8..76fc26a37eb 100644 --- a/app/jobs/serializers.py +++ b/app/jobs/serializers.py @@ -10,7 +10,7 @@ class JobSerializer(serializers.ModelSerializer): class Meta: model = Job fields = ( - 'title', 'description', 'github_profile_link', 'apply_url', + 'id', 'title', 'description', 'github_profile_link', 'apply_url', 'is_active', 'skills', 'expiry_date', 'location', - 'job_type' + 'job_type', 'url' ) diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html index c2747e033f5..1969fc0afb0 100644 --- a/app/jobs/templates/jobs/detail.html +++ b/app/jobs/templates/jobs/detail.html @@ -42,8 +42,10 @@
              {% trans "Be the OSS Funding you wish to see in the world." %}
              + +
              -

              Blockchain/Smart Contract Engineer

              +

              [[:title]]Blockchain/Smart Contract Engineer

              FULL-TIME
              Singapore
              @@ -117,6 +119,9 @@

              Description

              + + + @@ -139,7 +144,7 @@

              Description

              - + diff --git a/app/jobs/templates/jobs/list.html b/app/jobs/templates/jobs/list.html index 59f72164809..5a495aedda7 100644 --- a/app/jobs/templates/jobs/list.html +++ b/app/jobs/templates/jobs/list.html @@ -73,7 +73,7 @@

              {% trans "No results found." %}

              diff --git a/app/jobs/views.py b/app/jobs/views.py index 72c6d595b14..b7b3d84b2f2 100644 --- a/app/jobs/views.py +++ b/app/jobs/views.py @@ -2,21 +2,17 @@ from django.template.response import TemplateResponse # gitcoin-web imports -from .models import Job +# from .models import Job def list_jobs(request): - context = { - 'jobs': Job.objects.all() - } - + context = dict() return TemplateResponse(request, 'jobs/list.html', context=context) def job_detail(request, pk): - job = Job.objects.filter(pk=pk) context = { - 'job': job + 'pk': pk } return TemplateResponse(request, 'jobs/detail.html', context=context) From c6b7d57a3f52c128d81e3a6c8850f6e922f84788 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sat, 19 May 2018 21:45:10 +0530 Subject: [PATCH 05/32] Make Job detail view fetch data from API --- app/assets/v2/js/pages/job_details.js | 773 +----------------- .../migrations/0003_auto_20180519_1611.py | 23 + app/jobs/models.py | 1 + app/jobs/serializers.py | 4 +- app/jobs/templates/jobs/detail.html | 126 ++- 5 files changed, 86 insertions(+), 841 deletions(-) create mode 100644 app/jobs/migrations/0003_auto_20180519_1611.py diff --git a/app/assets/v2/js/pages/job_details.js b/app/assets/v2/js/pages/job_details.js index 442f4a185e5..693a57ea1cd 100644 --- a/app/assets/v2/js/pages/job_details.js +++ b/app/assets/v2/js/pages/job_details.js @@ -1,635 +1,12 @@ /* eslint block-scoped-var: "warn" */ /* eslint no-redeclare: "warn" */ - -var _truthy = function(val) { - if (!val || val == '0x0000000000000000000000000000000000000000') { - return false; - } - return true; -}; -var address_ize = function(key, val, result) { - if (!_truthy(val)) { - return [ null, null ]; - } - return [ key, '' + val + '' ]; -}; -var gitcoin_ize = function(key, val, result) { - if (!_truthy(val)) { - return [ null, null ]; - } - return [ key, '@' + val.replace('@', '') + '' ]; -}; -var email_ize = function(key, val, result) { - if (!_truthy(val)) { - return [ null, null ]; - } - return [ key, '' + val + '' ]; -}; -var hide_if_empty = function(key, val, result) { - if (!_truthy(val)) { - return [ null, null ]; - } - return [ key, val ]; -}; -var unknown_if_empty = function(key, val, result) { - if (!_truthy(val)) { - return [ key, 'Unknown' ]; - } - return [ key, val ]; -}; -var link_ize = function(key, val, result) { - if (!_truthy(val)) { - return [ null, null ]; - } - return [ key, '' + val + '' ]; -}; - -// rows in the 'about' page -var rows = [ - 'avatar_url', - 'issuer_avatar_url', - 'title', - 'github_url', - 'value_in_token', - 'value_in_eth', - 'value_in_usdt', - 'token_value_in_usdt', - 'token_value_time_peg', - 'web3_created', - 'status', - 'bounty_owner_address', - 'bounty_owner_email', - 'issue_description', - 'bounty_owner_github_username', - 'fulfillments', - 'network', - 'experience_level', - 'project_length', - 'bounty_type', - 'expires_date', - 'bounty_owner_name', - 'issue_keywords', - 'started_owners_username', - 'submitted_owners_username', - 'fulfilled_owners_username', - 'fulfillment_accepted_on' -]; -var heads = { - 'avatar_url': gettext('Issue'), - 'value_in_token': gettext('Issue Funding Info'), - 'bounty_owner_address': gettext('Funder'), - 'fulfiller_address': gettext('Submitter'), - 'experience_level': gettext('Meta') -}; -var callbacks = { - 'github_url': link_ize, - 'value_in_token': function(key, val, result) { - return [ 'amount', Math.round((parseInt(val) / Math.pow(10, document.decimals)) * 1000) / 1000 + ' ' + result['token_name'] ]; - }, - 'avatar_url': function(key, val, result) { - return [ 'avatar', '' ]; - }, - 'issuer_avatar_url': function(key, val, result) { - var username = result['bounty_owner_github_username'] ? result['bounty_owner_github_username'] : 'Self'; - - return [ 'issuer_avatar_url', '' ]; - }, - 'status': function(key, val, result) { - var ui_status = val; - - if (ui_status == 'open') { - ui_status = '' + gettext('OPEN ISSUE') + ''; - } - if (ui_status == 'started') { - ui_status = '' + gettext('work started') + ''; - } - if (ui_status == 'submitted') { - ui_status = '' + gettext('work submitted') + ''; - } - if (ui_status == 'done') { - ui_status = '' + gettext('done') + ''; - } - if (ui_status == 'cancelled') { - ui_status = '' + gettext('cancelled') + ''; - } - return [ 'status', ui_status ]; - }, - 'issue_description': function(key, val, result) { - var converter = new showdown.Converter(); - - val = val.replace(/script/ig, 'scr_i_pt'); - var ui_body = val; - - ui_body = converter.makeHtml(ui_body); - - return [ 'issue_description', ui_body ]; - }, - 'bounty_owner_address': address_ize, - 'bounty_owner_email': email_ize, - 'experience_level': unknown_if_empty, - 'project_length': unknown_if_empty, - 'bounty_type': unknown_if_empty, - 'bounty_owner_github_username': gitcoin_ize, - 'bounty_owner_name': function(key, val, result) { - return [ 'bounty_owner_name', result.metadata.fullName ]; - }, - 'issue_keywords': function(key, val, result) { - if (!result.metadata.issueKeywords || result.metadata.issueKeywords.length == 0) - return [ 'issue_keywords', null ]; - - var keywords = result.metadata.issueKeywords.split(','); - var tags = []; - - keywords.forEach(function(keyword) { - tags.push('
              ' + keyword + '
              '); - }); - return [ 'issue_keywords', tags ]; - }, - 'value_in_eth': function(key, val, result) { - if (result['token_name'] == 'ETH' || val === null) { - return [ null, null ]; - } - return [ 'Amount (ETH)', Math.round((parseInt(val) / Math.pow(10, 18)) * 1000) / 1000 ]; - }, - 'value_in_usdt': function(key, val, result) { - if (val === null) { - return [ null, null ]; - } - var rates_estimate = get_rates_estimate(val); - - $('#value_in_usdt_wrapper').attr('title', '
              ' + rates_estimate + '
              '); - - return [ 'Amount_usd', val ]; - }, - 'fulfillment_accepted_on': function(key, val, result) { - if (val === null || typeof val == 'undefined') { - $('#fulfillment_accepted_on_wrapper').addClass('hidden'); - return [ null, null ]; - } - var timePeg = timeDifference(new Date(), new Date(val), false, 60 * 60); - - return [ 'fulfillment_accepted_on', timePeg ]; - }, - 'network': function(key, val, result) { - if (val == 'mainnet') { - $('#network').addClass('hidden'); - return [ null, null ]; - } - var warning = 'WARNING: this is a ' + val + ' network bounty, and is NOT real money. To see mainnet bounties, go to the bounty explorer and search for mainnet bounties. '; - - return [ 'network', warning ]; - }, - 'token_value_time_peg': function(key, val, result) { - if (val === null || typeof val == 'undefined') { - $('#token_value_time_peg_wrapper').addClass('hidden'); - return [ null, null ]; - } - var timePeg = timeDifference(new Date(), new Date(val), false, 60 * 60); - - return [ 'token_value_time_peg', timePeg ]; - }, - 'token_value_in_usdt': function(key, val, result) { - if (val === null || typeof val == 'undefined') { - $('#value_in_usdt_wrapper').addClass('hidden'); - return [ null, null ]; - } - return [ 'Token_amount_usd', '$' + val + '/' + result['token_name'] ]; - }, - 'web3_created': function(key, val, result) { - return [ 'updated', timeDifference(new Date(result['now']), new Date(result['web3_created'])) ]; - }, - 'expires_date': function(key, val, result) { - var label = 'expires'; - - expires_date = new Date(val); - now = new Date(result['now']); - - var expiringInPercentage = 100 * ( - (now.getTime() - new Date(result['web3_created']).getTime()) / - (expires_date.getTime() - new Date(result['web3_created']).getTime())); - - if (expiringInPercentage > 100) { - expiringInPercentage = 100; - } - - $('.progress').css('width', expiringInPercentage + '%'); - var response = timeDifference(now, expires_date).split(' '); - - if (expires_date < new Date()) { - label = 'expired'; - if (result['is_open']) { - $('.timeleft').text('Expired'); - $('.progress-bar').addClass('expired'); - response = '' + response.join(' ') + ''; - } else { - $('#timer').hide(); - } - } else if (result['status'] === 'done' || result['status'] === 'cancelled') { - $('#timer').hide(); - } else { - response.shift(); - response = response.join(' '); - } - return [ label, response ]; - }, - 'started_owners_username': function(key, val, result) { - var started = []; - - if (result.interested) { - var interested = result.interested; - - interested.forEach(function(_interested, position) { - var name = (position == interested.length - 1) ? - _interested.profile.handle : _interested.profile.handle.concat(','); - - started.push(profileHtml(_interested.profile.handle, name)); - }); - if (started.length == 0) { - started.push(''); - } - } - return [ 'started_owners_username', started ]; - }, - 'submitted_owners_username': function(key, val, result) { - var accepted = []; - - if (result.fulfillments) { - var submitted = result.fulfillments; - - submitted.forEach(function(_submitted, position) { - var name = (position == submitted.length - 1) ? - _submitted.fulfiller_github_username : _submitted.fulfiller_github_username.concat(','); - - accepted.push(profileHtml(_submitted.fulfiller_github_username, name)); - }); - if (accepted.length == 0) { - accepted.push(''); - } - } - return [ 'submitted_owners_username', accepted ]; - }, - 'fulfilled_owners_username': function(key, val, result) { - var accepted = []; - var accepted_fufillments = []; - - if (result.fulfillments) { - var fulfillments = result.fulfillments; - - fulfillments.forEach(function(fufillment) { - if (fufillment.accepted == true) - accepted_fufillments.push(fufillment.fulfiller_github_username); - }); - if (accepted_fufillments.length == 0) { - accepted.push(''); - } else { - accepted_fufillments.forEach((github_username, position) => { - var name = (position == accepted_fufillments.length - 1) ? - github_username : github_username.concat(','); - - accepted.push(profileHtml(github_username, name)); - }); - } - } - return [ 'fulfilled_owners_username', accepted ]; - } -}; - -var isBountyOwner = function(result) { - var bountyAddress = result['bounty_owner_address']; - - return (typeof web3 != 'undefined' && (web3.eth.coinbase == bountyAddress)); -}; - -var update_title = function() { - document.original_title_text = $('title').text(); - setInterval(function() { - if (document.prepend_title == '(...)') { - document.prepend_title = '(*..)'; - } else if (document.prepend_title == '(*..)') { - document.prepend_title = '(.*.)'; - } else if (document.prepend_title == '(.*.)') { - document.prepend_title = '(..*)'; - } else { - document.prepend_title = '(...)'; - } - $('title').text(document.prepend_title + ' ' + document.original_title_text); - }, 2000); -}; - -var showWarningMessage = function(txid) { - - update_title(); - $('.interior .body').addClass('loading'); - - if (typeof txid != 'undefined' && txid.indexOf('0x') != -1) { - clearInterval(interval); - var link_url = etherscan_tx_url(txid); - - $('#transaction_url').attr('href', link_url); - } - - $('.left-rails').hide(); - $('#bounty_details').hide(); - $('#bounty_detail').hide(); - - $('.transaction-status').show(); - $('.waiting_room_entertainment').show(); - - var radioButtons = $('.sidebar_search input'); - - for (var i = radioButtons.length - 1; i >= 0; i--) { - radioButtons[i].disabled = true; - } - - var secondsBetweenQuoteChanges = 30; - - waitingRoomEntertainment(); - var interval = setInterval(waitingRoomEntertainment, secondsBetweenQuoteChanges * 1000); -}; - -var wait_for_tx_to_mine_and_then_ping_server = function() { - console.log('checking for updates'); - if (typeof document.pendingIssueMetadata != 'undefined') { - var txid = document.pendingIssueMetadata['txid']; - - console.log('waiting for web3 to be available'); - callFunctionWhenweb3Available(function() { - console.log('waiting for tx to be mined'); - callFunctionWhenTransactionMined(txid, function() { - console.log('tx mined'); - var data = { - url: document.issueURL, - txid: txid, - network: document.web3network - }; - var error = function(response) { - // refresh upon error - document.location.href = document.location.href; - }; - var success = function(response) { - if (response.status == '200') { - console.log('success from sync/web', response); - - // clear local data - localStorage[document.issueURL] = ''; - if (response['url']) { - document.location.href = response['url']; - } else { - document.location.href = document.location.href; - } - } else { - console.log('error from sync/web', response); - error(response); - } - }; - - console.log('syncing gitcoin with web3'); - var uri = '/sync/web3/'; - - $.ajax({ - type: 'POST', - url: uri, - data: data, - success: success, - error: error, - dataType: 'json' - }); - }); - }); - } -}; - -var attach_work_actions = function() { - $('body').delegate('a[href="/interested"], a[href="/uninterested"]', 'click', function(e) { - e.preventDefault(); - if ($(this).attr('href') == '/interested') { - show_interest_modal.call(this); - } else if (confirm(gettext('Are you sure you want to remove your interest?'))) { - $(this).attr('href', '/interested'); - $(this).find('span').text('Start Work'); - remove_interest(document.result['pk']); - } - - }); -}; - -var show_interest_modal = function() { - var self = this; - - setTimeout(function() { - $.get('/interest/modal', function(newHTML) { - var modal = $(newHTML).appendTo('body').modal({ - modalClass: 'modal add-interest-modal' - }); - - modal.on('submit', function(event) { - event.preventDefault(); - - var has_question = event.target[0].value; - var issue_message = event.target[2].value; - - var agree_precedence = event.target[3].checked; - var agree_not_to_abandon = event.target[4].checked; - - if (!agree_precedence) { - alert('You must agree to the precedence clause.'); - return false; - } - if (!agree_not_to_abandon) { - alert('You must agree not to keep the fulfiller updated on your progress.'); - return false; - } - - $(self).attr('href', '/uninterested'); - $(self).find('span').text(gettext('Stop Work')); - add_interest(document.result['pk'], { - has_question, - issue_message - }); - $.modal.close(); - }); - }); - }); -}; - - var build_detail_page = function(result) { + var template = $.templates('#job_detail'); - // setup - var decimals = 18; - var related_token_details = tokenAddressToDetails(result['token_address']); - - if (related_token_details && related_token_details.decimals) { - decimals = related_token_details.decimals; - } - document.decimals = decimals; - $('#bounty_details').css('display', 'inline'); - - // title - result['title'] = result['title'] ? result['title'] : result['github_url']; - $('.title').html(gettext('Funded Issue Details: ') + result['title']); - - // insert table onto page - for (var j = 0; j < rows.length; j++) { - var key = rows[j]; - var head = null; - var val = result[key]; - - if (heads[key]) { - head = heads[key]; - } - if (callbacks[key]) { - _result = callbacks[key](key, val, result); - val = _result[1]; - } - var _entry = { - 'head': head, - 'key': key, - 'val': val - }; - var id = '#' + key; - - if ($(id).length) { - $(id).html(val); - } - } - -}; - -var do_actions = function(result) { - // helper vars - var is_legacy = result['web3_type'] == 'legacy_gitcoin'; - var is_date_expired = (new Date(result['now']) > new Date(result['expires_date'])); - var is_status_expired = result['status'] == 'expired'; - var is_status_done = result['status'] == 'done'; - var is_status_cancelled = result['status'] == 'cancelled'; - var can_submit_after_expiration_date = result['can_submit_after_expiration_date']; - var is_still_on_happy_path = result['status'] == 'open' || result['status'] == 'started' || result['status'] == 'submitted' || (can_submit_after_expiration_date && result['status'] == 'expired'); - - // Find interest information - pull_interest_list(result['pk'], function(is_interested) { - - // which actions should we show? - var show_start_stop_work = is_still_on_happy_path; - var show_github_link = result['github_url'].substring(0, 4) == 'http'; - var show_submit_work = true; - var show_kill_bounty = !is_status_done && !is_status_expired && !is_status_cancelled; - var show_increase_bounty = !is_status_done && !is_status_expired && !is_status_cancelled; - var kill_bounty_enabled = isBountyOwner(result); - var submit_work_enabled = !isBountyOwner(result); - var start_stop_work_enabled = !isBountyOwner(result); - var increase_bounty_enabled = isBountyOwner(result); - var show_accept_submission = isBountyOwner(result) && !is_status_expired && !is_status_done && !is_status_expired; - - if (is_legacy) { - show_start_stop_work = false; - show_github_link = true; - show_submit_work = false; - show_kill_bounty = false; - show_accept_submission = false; - } - - // actions - var actions = []; - - if (show_submit_work) { - var enabled = submit_work_enabled; - var _entry = { - enabled: enabled, - href: result['action_urls']['fulfill'], - text: gettext('Submit Work'), - parent: 'right_actions', - title: gettext('Submit work for the funder to review'), - work_started: is_interested, - id: 'submit' - }; - - actions.push(_entry); - } - - if (show_start_stop_work) { - var enabled = start_stop_work_enabled; - var interest_entry = { - enabled: enabled, - href: is_interested ? '/uninterested' : '/interested', - text: is_interested ? gettext('Stop Work') : gettext('Start Work'), - parent: 'right_actions', - title: is_interested ? gettext('Notify the funder that you will not be working on this project') : gettext('Notify the funder that you would like to take on this project'), - color: is_interested ? 'white' : '', - id: 'interest' - }; - - actions.push(interest_entry); - } - - if (show_kill_bounty) { - var enabled = kill_bounty_enabled; - var _entry = { - enabled: enabled, - href: result['action_urls']['cancel'], - text: gettext('Cancel Bounty'), - parent: 'right_actions', - title: gettext('Cancel bounty and reclaim funds for this issue') - }; - - actions.push(_entry); - } - - var pending_acceptance = result.fulfillments.filter(fulfillment => fulfillment.accepted == false).length; - - if (show_accept_submission && pending_acceptance > 0) { - var enabled = show_accept_submission; - var _entry = { - enabled: enabled, - href: result['action_urls']['accept'], - text: gettext('Accept Submission'), - title: gettext('This will payout the bounty to the submitter.'), - parent: 'right_actions', - pending_acceptance: pending_acceptance - }; + html = template.render(result); - actions.push(_entry); - } - - if (show_increase_bounty) { - var enabled = increase_bounty_enabled; - var _entry = { - enabled: enabled, - href: result['action_urls']['increase'], - text: gettext('Add Contribution'), - parent: 'right_actions', - title: gettext('Increase the funding for this issue'), - color: 'white' - }; - - actions.push(_entry); - } - - if (show_github_link) { - var github_url = result['github_url']; - // hack to get around the renamed repo for piper's work. can't change the data layer since blockchain is immutable - - github_url = github_url.replace('pipermerriam/web3.py', 'ethereum/web3.py'); - github_url = github_url.replace('ethereum/browser-solidity', 'ethereum/remix-ide'); - var github_tooltip = gettext('View issue details and comments on Github'); - - var _entry = { - enabled: true, - href: github_url, - text: gettext('View On Github'), - parent: 'right_actions', - title: gettext('View issue details and comments on Github'), - comments: result['github_comments'], - color: 'white' - }; - - actions.push(_entry); - } - - render_actions(actions); - }); + $('#rendered_job_detail').html(html); }; var render_actions = function(actions) { @@ -642,159 +19,35 @@ var render_actions = function(actions) { } }; -var pull_bounty_from_api = function() { - var uri = '/actions/api/v0.1/bounties/?github_url=' + document.issueURL; +var pull_job_from_api = function() { + var uri = '/api/v0.1/jobs/' + document.job_id + '/'; - if (typeof document.issueNetwork != 'undefined') { - uri = uri + '&network=' + document.issueNetwork; - } - if (typeof document.issue_stdbounties_id != 'undefined') { - uri = uri + '&standard_bounties_id=' + document.issue_stdbounties_id; - } - $.get(uri, function(results) { - results = sanitizeAPIResults(results); + $.get(uri, function(result) { + result = sanitizeAPIResults(result); var nonefound = true; - // potentially make this a lot faster by only pulling the specific issue required - - for (var i = 0; i < results.length; i++) { - var result = results[i]; - // if the result from the database matches the one in question. - - if (normalizeURL(result['github_url']) == normalizeURL(document.issueURL)) { - nonefound = false; + if (result) { + nonefound = false; build_detail_page(result); - - do_actions(result); - - render_activity(result); - document.result = result; - return; - } + $('#rendered_job_detail').css('display', 'block'); } if (nonefound) { - $('#primary_view').css('display', 'none'); + $('#rendered_job_detail').css('display', 'none'); // is there a pending issue or not? $('.nonefound').css('display', 'block'); } }).fail(function() { _alert({message: gettext('got an error. please try again, or contact support@gitcoin.co')}, 'error'); - $('#primary_view').css('display', 'none'); + $('#rendered_job_detail').css('display', 'none'); }).always(function() { $('.loading').css('display', 'none'); }); }; - -var render_activity = function(result) { - var activities = []; - - if (result.fulfillments) { - result.fulfillments.forEach(function(fulfillment) { - var link = fulfillment['fulfiller_github_url'] ? " [View Work]" : ''; - - if (fulfillment.accepted == true) { - activities.push({ - name: fulfillment.fulfiller_github_username, - address: fulfillment.fulfiller_address, - email: fulfillment.fulfiller_email, - fulfillment_id: fulfillment.fulfillment_id, - text: gettext('Work Accepted') + link, - age: timeDifference(new Date(result['now']), new Date(fulfillment.accepted_on)), - status: 'accepted' - }); - } - activities.push({ - name: fulfillment.fulfiller_github_username, - text: gettext('Work Submitted') + link, - created_on: fulfillment.created_on, - age: timeDifference(new Date(result['now']), new Date(fulfillment.created_on)), - status: 'submitted' - }); - }); - } - - if (result.interested) { - result.interested.forEach(function(_interested) { - activities.push({ - profileId: _interested.profile.id, - name: _interested.profile.handle, - text: gettext('Work Started'), - created_on: _interested.created, - age: timeDifference(new Date(result['now']), new Date(_interested.created)), - status: 'started', - uninterest_possible: isBountyOwner(result) || document.isStaff - }); - }); - } - - activities = activities.slice().sort(function(a, b) { - return a['created_on'] < b['created_on'] ? -1 : 1; - }).reverse(); - - var html = '

              ' + gettext('There\'s no activity yet!') + '

              '; - - if (activities.length > 0) { - var template = $.templates('#activity_template'); - - html = template.render(activities); - } - $('#activities').html(html); - - activities.filter(function(activity) { - return activity.uninterest_possible; - }).forEach(function(activity) { - $('#remove-' + activity.name).click(() => { - uninterested(result.pk, activity.profileId); - return false; - }); - }); - -}; - var main = function() { setTimeout(function() { - // setup - attach_work_actions(); - - // pull issue URL - if (typeof document.issueURL == 'undefined') { - document.issueURL = getParam('url'); - } - $('#submitsolicitation a').attr('href', '/funding/new/?source=' + document.issueURL); - - // if theres a pending submission for this issue, show the warning message - // if not, pull the data from the API - var isPending = false; - - if (localStorage[document.issueURL]) { - // validate pending issue metadata - document.pendingIssueMetadata = JSON.parse(localStorage[document.issueURL]); - var is_metadata_valid = typeof document.pendingIssueMetadata != 'undefined' && document.pendingIssueMetadata !== null && typeof document.pendingIssueMetadata['timestamp'] != 'undefined'; - - if (is_metadata_valid) { - // validate that the pending tx is within the last little while - var then = parseInt(document.pendingIssueMetadata['timestamp']); - var now = timestamp(); - var acceptableTimeDeltaSeconds = 60 * 60; // 1 hour - var isWithinAcceptableTimeRange = (now - then) < acceptableTimeDeltaSeconds; - - if (isWithinAcceptableTimeRange) { - // update from web3 - var txid = document.pendingIssueMetadata['txid']; - - showWarningMessage(txid); - wait_for_tx_to_mine_and_then_ping_server(); - isPending = true; - } - } - } - // show the actual bounty page - if (!isPending) { - pull_bounty_from_api(); - } - + pull_job_from_api(); }, 100); }; diff --git a/app/jobs/migrations/0003_auto_20180519_1611.py b/app/jobs/migrations/0003_auto_20180519_1611.py new file mode 100644 index 00000000000..31d70a3f7c1 --- /dev/null +++ b/app/jobs/migrations/0003_auto_20180519_1611.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0.4 on 2018-05-19 16:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jobs', '0002_auto_20180505_1435'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='company', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Company'), + ), + migrations.AlterField( + model_name='job', + name='job_type', + field=models.CharField(choices=[('Full Time', 'Full-Time'), ('Part Time', 'Part-Time'), ('Contract', 'Contract')], max_length=50, verbose_name='Job Type'), + ), + ] diff --git a/app/jobs/models.py b/app/jobs/models.py index 8210eac839d..08a97a233bb 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -52,6 +52,7 @@ class Job(models.Model): expiry_date = models.DateTimeField( _('Expiry Date'), null=False, blank=False, default=get_expiry_time ) + company = models.CharField(_('Company'), max_length=50, null=True, blank=True) def get_absolute_url(self): """Get the absolute URL for the Job. diff --git a/app/jobs/serializers.py b/app/jobs/serializers.py index 76fc26a37eb..a69c9b6b84b 100644 --- a/app/jobs/serializers.py +++ b/app/jobs/serializers.py @@ -11,6 +11,6 @@ class Meta: model = Job fields = ( 'id', 'title', 'description', 'github_profile_link', 'apply_url', - 'is_active', 'skills', 'expiry_date', 'location', - 'job_type', 'url' + 'is_active', 'skills', 'expiry_date', 'location', 'job_type', 'url', + 'company' ) diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html index 1969fc0afb0..a5fa1215703 100644 --- a/app/jobs/templates/jobs/detail.html +++ b/app/jobs/templates/jobs/detail.html @@ -42,84 +42,55 @@
              {% trans "Be the OSS Funding you wish to see in the world." %}
              - - -
              -

              [[:title]]Blockchain/Smart Contract Engineer

              -
              -
              FULL-TIME
              -
              Singapore
              -
              -
              -
              -
              Company: Truffle
              -
              Experience: Java
              -
              Posted: 2 days ago
              -
              - - Apply on Company Site - - - Github Profile - -

              Description

              -
              - We're looking for an experienced blockchain engineer to lead the development efforts of our Solidity smart contracts. - - Our mission is to allow buyers and sellers to meet and transact in a completely distributed way (from car, housing, bike, and other rentals to task-based services like freelance engineering, design, marketing and more). - - Role - - You will work closely with our founders (both developers) and other early engineers to architect and build out the Origin platform. - - You will develop the smart contracts for integral parts of our distributed platform including: - - User and listing registries - Handling calendaring, booking rules, identity verification and other features of the Origin platform. - Implementing our token economic models - As one of our first dedicated Solidity engineers, you will be integral in setting both company and engineering culture and processes. - - Unlike many other projects, we are looking to build large-scale, impactful infrastructure on top of the Ethereum blockchain (vs. just token sale contracts). Your work will be integral in changing the way that buyers and sellers interact on the blockchain. - - Requirements - - 2+ years in software engineering - Deep understanding and experience with writing Ethereum smart contracts - We have a strong preference for candidates who have contributed to open-source projects in the past - Bonus points if have demonstrated open-source leadership and led a major open-source project yourself - How to apply - - Please include the following in your resume and cover letter: - Links to online profiles you use (GitHub, Twitter, etc) - A description of your work history - Any publicly viewable code that you have written and published to the blockchain - Why you are excited about Origin - We strongly encourage applicants to read our product brief and whitepaper and to engage with us around specific technical challenges. -
              -
              -
              python
              -
              django
              -
              javascript
              -
              css
              -
              -
              -
              -
              -
              -
              -
              - Truffle -
              -
              -
              Email
              -
              -
              -
              Gitcoin Profile
              +
              + +
              @@ -133,10 +104,7 @@

              Description

              From 58afbac7635bbcce2d43cc42f8c21cf099a4d845 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sun, 20 May 2018 01:13:26 +0530 Subject: [PATCH 06/32] Fix linting + more dynamic render on job details page --- app/assets/v2/css/jobs.css | 5 ++++ app/assets/v2/js/pages/job_details.js | 26 ++++++------------- app/assets/v2/js/pages/jobs.js | 9 +------ app/jobs/migrations/0001_initial.py | 6 +++-- .../migrations/0002_auto_20180505_1435.py | 23 ---------------- app/jobs/migrations/0002_job_apply_email.py | 18 +++++++++++++ .../migrations/0003_auto_20180519_1611.py | 23 ---------------- .../0003_job_posted_by_gitcoin_username.py | 18 +++++++++++++ app/jobs/models.py | 13 +++++++++- app/jobs/serializers.py | 3 ++- app/jobs/templates/jobs/detail.html | 20 ++++++++------ app/jobs/templates/jobs/list.html | 4 +-- .../migrations/0023_auto_20180519_1900.py | 18 +++++++++++++ 13 files changed, 100 insertions(+), 86 deletions(-) delete mode 100644 app/jobs/migrations/0002_auto_20180505_1435.py create mode 100644 app/jobs/migrations/0002_job_apply_email.py delete mode 100644 app/jobs/migrations/0003_auto_20180519_1611.py create mode 100644 app/jobs/migrations/0003_job_posted_by_gitcoin_username.py create mode 100644 app/marketing/migrations/0023_auto_20180519_1900.py diff --git a/app/assets/v2/css/jobs.css b/app/assets/v2/css/jobs.css index 3e32ff18c8d..a28b72da96e 100644 --- a/app/assets/v2/css/jobs.css +++ b/app/assets/v2/css/jobs.css @@ -11,6 +11,10 @@ h4 { text-align: left; } +.job-title { + padding-top: 2em; +} + .job-header { padding-bottom: 6em; } @@ -61,6 +65,7 @@ h4 { flex-direction: row; justify-content: space-between; margin: 1em 0 1em 0; + padding-bottom: 2em; } .job-posted { diff --git a/app/assets/v2/js/pages/job_details.js b/app/assets/v2/js/pages/job_details.js index 693a57ea1cd..9cd96734f15 100644 --- a/app/assets/v2/js/pages/job_details.js +++ b/app/assets/v2/js/pages/job_details.js @@ -2,21 +2,11 @@ /* eslint no-redeclare: "warn" */ var build_detail_page = function(result) { - var template = $.templates('#job_detail'); + var template = $.templates('#job_detail'); - html = template.render(result); + html = template.render(result); - $('#rendered_job_detail').html(html); -}; - -var render_actions = function(actions) { - for (var l = 0; l < actions.length; l++) { - var target = actions[l]['parent']; - var tmpl = $.templates('#action'); - var html = tmpl.render(actions[l]); - - $('#' + target).append(html); - } + $('#rendered_job_detail').html(html); }; var pull_job_from_api = function() { @@ -27,10 +17,10 @@ var pull_job_from_api = function() { var nonefound = true; if (result) { - nonefound = false; - build_detail_page(result); - document.result = result; - $('#rendered_job_detail').css('display', 'block'); + nonefound = false; + build_detail_page(result); + document.result = result; + $('#rendered_job_detail').css('display', 'block'); } if (nonefound) { $('#rendered_job_detail').css('display', 'none'); @@ -47,7 +37,7 @@ var pull_job_from_api = function() { var main = function() { setTimeout(function() { - pull_job_from_api(); + pull_job_from_api(); }, 100); }; diff --git a/app/assets/v2/js/pages/jobs.js b/app/assets/v2/js/pages/jobs.js index d106ffc9e0b..49ce00efda6 100644 --- a/app/assets/v2/js/pages/jobs.js +++ b/app/assets/v2/js/pages/jobs.js @@ -419,13 +419,6 @@ var refreshjobs = function(event) { result['rounded_amount'] = Math.round(result['value_in_token'] / divisor * 100) / 100; var is_expired = new Date(result['expires_date']) < new Date() && !result['is_open']; - // setup args to go into template - // if (typeof web3 != 'undefined' && web3.eth.coinbase == result['bounty_owner_address']) { - // result['my_bounty'] = 'mine'; - // } else if (result['fulfiller_address'] !== '0x0000000000000000000000000000000000000000') { - // result['my_bounty'] = '' + result['status'] + ''; - // } - console.log(result); result.action = result['url']; result['title'] = result['title'] ? result['title'] : result['github_url']; @@ -433,7 +426,7 @@ var refreshjobs = function(event) { result['job_company'] = ((result['company'] ? result['company'] : 'Company Hidden') + ' • '); - result['job_skill'] += result['skills'] ? result['skills'] : '' + result['job_skill'] += result['skills'] ? result['skills'] : ''; result['watch'] = 'Watch'; diff --git a/app/jobs/migrations/0001_initial.py b/app/jobs/migrations/0001_initial.py index 35f4faafcfc..d6715f1b972 100644 --- a/app/jobs/migrations/0001_initial.py +++ b/app/jobs/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.4 on 2018-05-05 14:14 +# Generated by Django 2.0.5 on 2018-05-19 18:38 import django.contrib.postgres.fields from django.db import migrations, models @@ -20,11 +20,13 @@ class Migration(migrations.Migration): ('title', models.CharField(max_length=200, verbose_name='Title')), ('description', models.TextField(verbose_name='Description')), ('github_profile_link', models.URLField(blank=True, verbose_name='Github Profile Link')), - ('job_type', models.CharField(max_length=50, verbose_name='job type')), + ('location', models.CharField(blank=True, max_length=50, verbose_name='Location')), + ('job_type', models.CharField(choices=[('Full Time', 'Full-Time'), ('Part Time', 'Part-Time'), ('Contract', 'Contract')], max_length=50, verbose_name='Job Type')), ('apply_url', models.URLField(blank=True)), ('is_active', models.BooleanField(default=False, verbose_name='Is this job active?')), ('skills', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=30, verbose_name='skill'), blank=True, null=True, size=None)), ('expiry_date', models.DateTimeField(default=jobs.models.get_expiry_time, verbose_name='Expiry Date')), + ('company', models.CharField(blank=True, max_length=50, null=True, verbose_name='Company')), ], options={ 'verbose_name': 'Job', diff --git a/app/jobs/migrations/0002_auto_20180505_1435.py b/app/jobs/migrations/0002_auto_20180505_1435.py deleted file mode 100644 index efa5a377489..00000000000 --- a/app/jobs/migrations/0002_auto_20180505_1435.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.0.4 on 2018-05-05 14:35 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('jobs', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='job', - name='location', - field=models.CharField(blank=True, max_length=50, verbose_name='Location'), - ), - migrations.AlterField( - model_name='job', - name='job_type', - field=models.CharField(choices=[('Full Time', 'Full Time'), ('Part Time', 'Part Time')], max_length=50, verbose_name='job type'), - ), - ] diff --git a/app/jobs/migrations/0002_job_apply_email.py b/app/jobs/migrations/0002_job_apply_email.py new file mode 100644 index 00000000000..9293f7f5405 --- /dev/null +++ b/app/jobs/migrations/0002_job_apply_email.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.5 on 2018-05-19 19:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jobs', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='apply_email', + field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Contact Email for Job'), + ), + ] diff --git a/app/jobs/migrations/0003_auto_20180519_1611.py b/app/jobs/migrations/0003_auto_20180519_1611.py deleted file mode 100644 index 31d70a3f7c1..00000000000 --- a/app/jobs/migrations/0003_auto_20180519_1611.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.0.4 on 2018-05-19 16:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('jobs', '0002_auto_20180505_1435'), - ] - - operations = [ - migrations.AddField( - model_name='job', - name='company', - field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Company'), - ), - migrations.AlterField( - model_name='job', - name='job_type', - field=models.CharField(choices=[('Full Time', 'Full-Time'), ('Part Time', 'Part-Time'), ('Contract', 'Contract')], max_length=50, verbose_name='Job Type'), - ), - ] diff --git a/app/jobs/migrations/0003_job_posted_by_gitcoin_username.py b/app/jobs/migrations/0003_job_posted_by_gitcoin_username.py new file mode 100644 index 00000000000..653e3038601 --- /dev/null +++ b/app/jobs/migrations/0003_job_posted_by_gitcoin_username.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.5 on 2018-05-19 19:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jobs', '0002_job_apply_email'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='posted_by_gitcoin_username', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Username of person who posted Job'), + ), + ] diff --git a/app/jobs/models.py b/app/jobs/models.py index 08a97a233bb..6860d331dd5 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -1,8 +1,9 @@ from datetime import timedelta -from django.contrib.postgres.fields import ArrayField from django.conf import settings +from django.contrib.postgres.fields import ArrayField from django.db import models +from django.urls import reverse from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -53,6 +54,16 @@ class Job(models.Model): _('Expiry Date'), null=False, blank=False, default=get_expiry_time ) company = models.CharField(_('Company'), max_length=50, null=True, blank=True) + apply_email = models.EmailField(_('Contact Email for Job'), null=True, blank=True) + posted_by_gitcoin_username = models.CharField( + _('Username of person who posted Job'), max_length=50, null=True, blank=True + ) + + @property + def posted_by_user_profile_url(self): + if self.posted_by_gitcoin_username: + return reverse('profile', args=[self.posted_by_gitcoin_username]) + return None def get_absolute_url(self): """Get the absolute URL for the Job. diff --git a/app/jobs/serializers.py b/app/jobs/serializers.py index a69c9b6b84b..c385f7114c5 100644 --- a/app/jobs/serializers.py +++ b/app/jobs/serializers.py @@ -12,5 +12,6 @@ class Meta: fields = ( 'id', 'title', 'description', 'github_profile_link', 'apply_url', 'is_active', 'skills', 'expiry_date', 'location', 'job_type', 'url', - 'company' + 'company', 'apply_email', 'posted_by_user_profile_url', + 'posted_by_gitcoin_username' ) diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html index a5fa1215703..e2fcad99c89 100644 --- a/app/jobs/templates/jobs/detail.html +++ b/app/jobs/templates/jobs/detail.html @@ -54,7 +54,7 @@

              [[:title]]

              Company: [[:company]]
              -
              Experience: Java
              +
              Experience: [[:skills]]
              Posted: 2 days ago
              @@ -68,10 +68,7 @@

              Description

              [[:description]]
              -
              python
              -
              django
              -
              javascript
              -
              css
              +
              [[:skills]]
              @@ -82,17 +79,24 @@

              Description

              [[:company]]
              -
              Gitcoin Profile
              +
              Gitcoin Profile + + [[if posted_by_user_profile_url]] + @[[:posted_by_gitcoin_username]] + [[else]] + Not available + [[/if]] + +
              - diff --git a/app/jobs/templates/jobs/list.html b/app/jobs/templates/jobs/list.html index 5a495aedda7..221cfc905bd 100644 --- a/app/jobs/templates/jobs/list.html +++ b/app/jobs/templates/jobs/list.html @@ -93,8 +93,8 @@

              {% trans "No results found." %}

              [[:job_skill]]
              -
              Contract
              -
              Singapore
              +
              [[:job_type]]
              +
              [[:location]]
              diff --git a/app/marketing/migrations/0023_auto_20180519_1900.py b/app/marketing/migrations/0023_auto_20180519_1900.py new file mode 100644 index 00000000000..eace4410ede --- /dev/null +++ b/app/marketing/migrations/0023_auto_20180519_1900.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.5 on 2018-05-19 19:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('marketing', '0022_alumni'), + ] + + operations = [ + migrations.AlterField( + model_name='alumni', + name='comments', + field=models.TextField(blank=True, max_length=5000), + ), + ] From 6156a0726d75f7c977c1a23d3f67102cb42db1d0 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Fri, 25 May 2018 23:18:57 +0530 Subject: [PATCH 07/32] WIP job creation --- app/app/urls.py | 1 + app/jobs/forms.py | 13 ++- app/jobs/models.py | 11 +-- app/jobs/serializers.py | 7 +- app/jobs/templates/jobs/create_job.html | 101 ++++++++++++++++++++++++ app/jobs/templates/jobs/detail.html | 4 +- app/jobs/templates/jobs/list.html | 3 + app/jobs/views.py | 15 +++- 8 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 app/jobs/templates/jobs/create_job.html diff --git a/app/app/urls.py b/app/app/urls.py index 1f228d773d7..abb5dca758e 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -260,6 +260,7 @@ # Job urls goes below path('jobs', jobs.views.list_jobs, name='job-list-template'), path('jobs//', jobs.views.job_detail, name='job-detail-template'), + path('jobs/new/', jobs.views.create_job, name='job-create-template'), ] if settings.ENABLE_SILK: diff --git a/app/jobs/forms.py b/app/jobs/forms.py index 01071e5070d..67b421856b1 100644 --- a/app/jobs/forms.py +++ b/app/jobs/forms.py @@ -17,19 +17,24 @@ along with this program. If not, see . """ -from django.forms import ModelForm +from django import forms +from dashboard.views import profile_keywords_helper from .models import Job -class JobForm(ModelForm): +class JobForm(forms.ModelForm): """Define the Job form handling.""" + skills = forms.ChoiceField(choices=(), required=True) + + def __init__(self, user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['skills'].choices = profile_keywords_helper(user) class Meta: """Define the JOB form metadata.""" model = Job fields = [ - 'title', 'description', 'github_profile_link', 'apply_url', - 'is_active', 'skills', 'expiry_date' + 'title', 'job_type', 'location', 'skills', 'company', 'description' ] diff --git a/app/jobs/models.py b/app/jobs/models.py index 6860d331dd5..5cc628264e9 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -9,9 +9,6 @@ # Create your models here. -SKILL_CHOICES = ( -) - JOB_TYPE_CHOICES = ( ('Full Time', 'Full-Time'), ('Part Time', 'Part-Time'), @@ -43,12 +40,8 @@ class Job(models.Model): verbose_name=_('Is this job active?'), default=False, null=False, blank=True ) - skills = ArrayField( - models.CharField( - verbose_name=_('skill'), - choices=SKILL_CHOICES, max_length=30, null=False, blank=True - ), - null=True, blank=True + skills = models.CharField( + verbose_name=_('skill'), max_length=60, null=False, blank=True ) expiry_date = models.DateTimeField( _('Expiry Date'), null=False, blank=False, default=get_expiry_time diff --git a/app/jobs/serializers.py b/app/jobs/serializers.py index c385f7114c5..7a433f140bc 100644 --- a/app/jobs/serializers.py +++ b/app/jobs/serializers.py @@ -1,11 +1,16 @@ # Third Party Stuff +from django.urls import reverse from rest_framework import serializers from .models import Job class JobSerializer(serializers.ModelSerializer): + company_avatar = serializers.SerializerMethodField() + + def get_company_avatar(self, obj): + return reverse('org_avatar', args=[obj.posted_by_gitcoin_username]) class Meta: model = Job @@ -13,5 +18,5 @@ class Meta: 'id', 'title', 'description', 'github_profile_link', 'apply_url', 'is_active', 'skills', 'expiry_date', 'location', 'job_type', 'url', 'company', 'apply_email', 'posted_by_user_profile_url', - 'posted_by_gitcoin_username' + 'posted_by_gitcoin_username', 'company_avatar' ) diff --git a/app/jobs/templates/jobs/create_job.html b/app/jobs/templates/jobs/create_job.html new file mode 100644 index 00000000000..2af2e1b68f9 --- /dev/null +++ b/app/jobs/templates/jobs/create_job.html @@ -0,0 +1,101 @@ +{% comment %} + Copyright (C) 2017 Gitcoin Core + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +{% endcomment %} +{% load i18n static %} + + + + + {% include 'shared/head.html' %} + + + + + {% include 'shared/tag_manager_2.html' %} +
              + {% include 'shared/nav.html' %} +
              + +
              +
              +
              +
              +
              + {% csrf_token %} +
              +
              {{ form.title.label_tag }}
              +
              + {{ form.title.errors }} + {{ form.title }} +
              + +
              {{ form.job_type.label_tag }}
              +
              + {{ form.job_type.errors }} + {{ form.job_type }} +
              + +
              {{ form.location.label_tag }}
              +
              + {{ form.location.errors }} + {{ form.location }} +
              + +
              {{ form.skills.label_tag }}
              +
              + {{ form.skills.errors }} + {{ form.skills }} +
              + +
              {{ form.company.label_tag }}
              +
              + {{ form.company.errors }} + {{ form.company }} +
              + +
              {{ form.description.label_tag }}
              +
              + {{ form.description.errors }} + {{ form.description }} +
              + + +
              +
              +
              +
              + {% include 'shared/analytics.html' %} + {% include 'shared/footer_scripts.html' %} + {% include 'shared/rollbar.html' %} + {% include 'shared/footer.html' %} + + + + + + + + + + + + + diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html index e2fcad99c89..702b3bafd50 100644 --- a/app/jobs/templates/jobs/detail.html +++ b/app/jobs/templates/jobs/detail.html @@ -18,7 +18,7 @@ @@ -73,7 +73,7 @@

              Description

              -
              +
              [[:company]] diff --git a/app/jobs/templates/jobs/list.html b/app/jobs/templates/jobs/list.html index 221cfc905bd..6cc1bf50c40 100644 --- a/app/jobs/templates/jobs/list.html +++ b/app/jobs/templates/jobs/list.html @@ -85,6 +85,9 @@

              {% trans "No results found." %}

              [[/if]]
              +
              + +
              [[:title]]
              diff --git a/app/jobs/views.py b/app/jobs/views.py index b7b3d84b2f2..a994886cff4 100644 --- a/app/jobs/views.py +++ b/app/jobs/views.py @@ -1,8 +1,9 @@ # Third-Party imports +from django.shortcuts import render from django.template.response import TemplateResponse # gitcoin-web imports -# from .models import Job +from .forms import JobForm def list_jobs(request): @@ -16,3 +17,15 @@ def job_detail(request, pk): } return TemplateResponse(request, 'jobs/detail.html', context=context) + + +def create_job(request): + if request.method == 'POST': + form = JobForm(request.POST) + if form.is_valid(): + # import ipdb; ipdb.set_trace() + form.save() + print("saved") + else: + form = JobForm(user=request.user) + return render(request, 'jobs/create_job.html', {'form': form}) From 786d983d6d5a87db907aa297cddb5f572c2060e8 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sun, 27 May 2018 14:27:14 +0530 Subject: [PATCH 08/32] Fix skills to be fetched from keywords --- app/dashboard/views.py | 2 +- app/jobs/forms.py | 2 +- app/jobs/migrations/0004_auto_20180527_0856.py | 18 ++++++++++++++++++ app/jobs/models.py | 3 +-- app/jobs/views.py | 2 +- 5 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 app/jobs/migrations/0004_auto_20180527_0856.py diff --git a/app/dashboard/views.py b/app/dashboard/views.py index c1612b42ce5..038ab464306 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -782,7 +782,7 @@ def profile_keywords_helper(handle): """ profile = profile_helper(handle, True) - keywords = [] + keywords = ['java', 'python'] for repo in profile.repos_data: language = repo.get('language') if repo.get('language') else '' _keywords = language.split(',') diff --git a/app/jobs/forms.py b/app/jobs/forms.py index 67b421856b1..067132c411d 100644 --- a/app/jobs/forms.py +++ b/app/jobs/forms.py @@ -29,7 +29,7 @@ class JobForm(forms.ModelForm): def __init__(self, user, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['skills'].choices = profile_keywords_helper(user) + self.fields['skills'].choices = [(x, x) for x in profile_keywords_helper(user.profile.handle)] class Meta: """Define the JOB form metadata.""" diff --git a/app/jobs/migrations/0004_auto_20180527_0856.py b/app/jobs/migrations/0004_auto_20180527_0856.py new file mode 100644 index 00000000000..01366c95903 --- /dev/null +++ b/app/jobs/migrations/0004_auto_20180527_0856.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.5 on 2018-05-27 08:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jobs', '0003_job_posted_by_gitcoin_username'), + ] + + operations = [ + migrations.AlterField( + model_name='job', + name='skills', + field=models.CharField(blank=True, max_length=60, null=True, verbose_name='skill'), + ), + ] diff --git a/app/jobs/models.py b/app/jobs/models.py index 5cc628264e9..d025134c956 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -1,7 +1,6 @@ from datetime import timedelta from django.conf import settings -from django.contrib.postgres.fields import ArrayField from django.db import models from django.urls import reverse from django.utils import timezone @@ -41,7 +40,7 @@ class Job(models.Model): null=False, blank=True ) skills = models.CharField( - verbose_name=_('skill'), max_length=60, null=False, blank=True + verbose_name=_('skill'), max_length=60, null=True, blank=True ) expiry_date = models.DateTimeField( _('Expiry Date'), null=False, blank=False, default=get_expiry_time diff --git a/app/jobs/views.py b/app/jobs/views.py index a994886cff4..2f30741752a 100644 --- a/app/jobs/views.py +++ b/app/jobs/views.py @@ -21,7 +21,7 @@ def job_detail(request, pk): def create_job(request): if request.method == 'POST': - form = JobForm(request.POST) + form = JobForm(user=request.user, data=request.POST) if form.is_valid(): # import ipdb; ipdb.set_trace() form.save() From b28e6ad8de8c2a13f5e30e6bfca316f9e13e1aa9 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Mon, 28 May 2018 23:41:26 +0530 Subject: [PATCH 09/32] Fix header in create job page --- app/jobs/templates/jobs/create_job.html | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app/jobs/templates/jobs/create_job.html b/app/jobs/templates/jobs/create_job.html index 2af2e1b68f9..94582a5b8e0 100644 --- a/app/jobs/templates/jobs/create_job.html +++ b/app/jobs/templates/jobs/create_job.html @@ -25,13 +25,29 @@ #new-job-form { padding-left: 200px; } + + .job-header { + padding-bottom: 6em; + } + + #job-board-title { + text-transform: uppercase; + color: #00A55E; + text-align: center; + font-size: 3em; + } + + #new-job-form { + padding-top: 3em; + } {% include 'shared/tag_manager_2.html' %} -
              - {% include 'shared/nav.html' %} +
              + {% include 'shared/nav.html' %} +
              Post a Job
              From 06053826d99de5c34438bcb331fa0b3a4e2cc0a2 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Tue, 29 May 2018 00:18:24 +0530 Subject: [PATCH 10/32] Fix posted_at date in job detail view --- app/assets/v2/js/pages/job_details.js | 1 + app/jobs/migrations/0005_job_posted_at.py | 19 +++++++++++++++++++ app/jobs/models.py | 1 + app/jobs/serializers.py | 2 +- app/jobs/templates/jobs/detail.html | 6 ++---- 5 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 app/jobs/migrations/0005_job_posted_at.py diff --git a/app/assets/v2/js/pages/job_details.js b/app/assets/v2/js/pages/job_details.js index 9cd96734f15..1e253bd5b6b 100644 --- a/app/assets/v2/js/pages/job_details.js +++ b/app/assets/v2/js/pages/job_details.js @@ -2,6 +2,7 @@ /* eslint no-redeclare: "warn" */ var build_detail_page = function(result) { + result['posted_at'] = timeDifference(new Date(), new Date(result['posted_at'])); var template = $.templates('#job_detail'); html = template.render(result); diff --git a/app/jobs/migrations/0005_job_posted_at.py b/app/jobs/migrations/0005_job_posted_at.py new file mode 100644 index 00000000000..9a5bc8cc09e --- /dev/null +++ b/app/jobs/migrations/0005_job_posted_at.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.5 on 2018-05-28 18:20 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('jobs', '0004_auto_20180527_0856'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='posted_at', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Posted At'), + ), + ] diff --git a/app/jobs/models.py b/app/jobs/models.py index d025134c956..ce01302e213 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -47,6 +47,7 @@ class Job(models.Model): ) company = models.CharField(_('Company'), max_length=50, null=True, blank=True) apply_email = models.EmailField(_('Contact Email for Job'), null=True, blank=True) + posted_at = models.DateTimeField(_('Posted At'), null=False, blank=False, default=timezone.now) posted_by_gitcoin_username = models.CharField( _('Username of person who posted Job'), max_length=50, null=True, blank=True ) diff --git a/app/jobs/serializers.py b/app/jobs/serializers.py index 7a433f140bc..4f0fe5018cd 100644 --- a/app/jobs/serializers.py +++ b/app/jobs/serializers.py @@ -18,5 +18,5 @@ class Meta: 'id', 'title', 'description', 'github_profile_link', 'apply_url', 'is_active', 'skills', 'expiry_date', 'location', 'job_type', 'url', 'company', 'apply_email', 'posted_by_user_profile_url', - 'posted_by_gitcoin_username', 'company_avatar' + 'posted_by_gitcoin_username', 'company_avatar', 'posted_at' ) diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html index 702b3bafd50..2a94ceeedb4 100644 --- a/app/jobs/templates/jobs/detail.html +++ b/app/jobs/templates/jobs/detail.html @@ -55,7 +55,7 @@

              [[:title]]

              Company: [[:company]]
              Experience: [[:skills]]
              -
              Posted: 2 days ago
              +
              Posted: [[:posted_at]]
              Apply on Company Site @@ -114,11 +114,9 @@

              Description

              - - - + From 3fbac5f61fd9721c137adbf277928c756a3c1c82 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sun, 3 Jun 2018 16:14:59 +0530 Subject: [PATCH 11/32] Fix migrations for jobs app --- app/assets/v2/css/jobs.css | 4 +++ app/jobs/migrations/0001_initial.py | 9 ++++-- app/jobs/migrations/0002_job_apply_email.py | 18 ------------ .../0003_job_posted_by_gitcoin_username.py | 18 ------------ .../migrations/0004_auto_20180527_0856.py | 18 ------------ app/jobs/migrations/0005_job_posted_at.py | 19 ------------- app/jobs/templates/jobs/detail.html | 4 +-- app/jobs/templates/jobs/sidebar_search.html | 28 +++++++++---------- 8 files changed, 26 insertions(+), 92 deletions(-) delete mode 100644 app/jobs/migrations/0002_job_apply_email.py delete mode 100644 app/jobs/migrations/0003_job_posted_by_gitcoin_username.py delete mode 100644 app/jobs/migrations/0004_auto_20180527_0856.py delete mode 100644 app/jobs/migrations/0005_job_posted_at.py diff --git a/app/assets/v2/css/jobs.css b/app/assets/v2/css/jobs.css index a28b72da96e..e9cf001e0af 100644 --- a/app/assets/v2/css/jobs.css +++ b/app/assets/v2/css/jobs.css @@ -121,3 +121,7 @@ h4 { background-color: #F2F6F9; color: #6184AC; } + +#company-avatar-img { + width: 2em; +} diff --git a/app/jobs/migrations/0001_initial.py b/app/jobs/migrations/0001_initial.py index d6715f1b972..f5dc29c0458 100644 --- a/app/jobs/migrations/0001_initial.py +++ b/app/jobs/migrations/0001_initial.py @@ -1,7 +1,7 @@ -# Generated by Django 2.0.5 on 2018-05-19 18:38 +# Generated by Django 2.0.5 on 2018-06-03 10:44 -import django.contrib.postgres.fields from django.db import migrations, models +import django.utils.timezone import jobs.models @@ -24,9 +24,12 @@ class Migration(migrations.Migration): ('job_type', models.CharField(choices=[('Full Time', 'Full-Time'), ('Part Time', 'Part-Time'), ('Contract', 'Contract')], max_length=50, verbose_name='Job Type')), ('apply_url', models.URLField(blank=True)), ('is_active', models.BooleanField(default=False, verbose_name='Is this job active?')), - ('skills', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=30, verbose_name='skill'), blank=True, null=True, size=None)), + ('skills', models.CharField(blank=True, max_length=60, null=True, verbose_name='skill')), ('expiry_date', models.DateTimeField(default=jobs.models.get_expiry_time, verbose_name='Expiry Date')), ('company', models.CharField(blank=True, max_length=50, null=True, verbose_name='Company')), + ('apply_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Contact Email for Job')), + ('posted_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Posted At')), + ('posted_by_gitcoin_username', models.CharField(blank=True, max_length=50, null=True, verbose_name='Username of person who posted Job')), ], options={ 'verbose_name': 'Job', diff --git a/app/jobs/migrations/0002_job_apply_email.py b/app/jobs/migrations/0002_job_apply_email.py deleted file mode 100644 index 9293f7f5405..00000000000 --- a/app/jobs/migrations/0002_job_apply_email.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.0.5 on 2018-05-19 19:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('jobs', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='job', - name='apply_email', - field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Contact Email for Job'), - ), - ] diff --git a/app/jobs/migrations/0003_job_posted_by_gitcoin_username.py b/app/jobs/migrations/0003_job_posted_by_gitcoin_username.py deleted file mode 100644 index 653e3038601..00000000000 --- a/app/jobs/migrations/0003_job_posted_by_gitcoin_username.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.0.5 on 2018-05-19 19:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('jobs', '0002_job_apply_email'), - ] - - operations = [ - migrations.AddField( - model_name='job', - name='posted_by_gitcoin_username', - field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Username of person who posted Job'), - ), - ] diff --git a/app/jobs/migrations/0004_auto_20180527_0856.py b/app/jobs/migrations/0004_auto_20180527_0856.py deleted file mode 100644 index 01366c95903..00000000000 --- a/app/jobs/migrations/0004_auto_20180527_0856.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.0.5 on 2018-05-27 08:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('jobs', '0003_job_posted_by_gitcoin_username'), - ] - - operations = [ - migrations.AlterField( - model_name='job', - name='skills', - field=models.CharField(blank=True, max_length=60, null=True, verbose_name='skill'), - ), - ] diff --git a/app/jobs/migrations/0005_job_posted_at.py b/app/jobs/migrations/0005_job_posted_at.py deleted file mode 100644 index 9a5bc8cc09e..00000000000 --- a/app/jobs/migrations/0005_job_posted_at.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.0.5 on 2018-05-28 18:20 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('jobs', '0004_auto_20180527_0856'), - ] - - operations = [ - migrations.AddField( - model_name='job', - name='posted_at', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Posted At'), - ), - ] diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html index 2a94ceeedb4..c9bea118fbf 100644 --- a/app/jobs/templates/jobs/detail.html +++ b/app/jobs/templates/jobs/detail.html @@ -71,9 +71,9 @@

              Description

              [[:skills]]
              -
              +
              -
              +
              [[:company]] diff --git a/app/jobs/templates/jobs/sidebar_search.html b/app/jobs/templates/jobs/sidebar_search.html index e5c533b68f0..b8b163ada54 100644 --- a/app/jobs/templates/jobs/sidebar_search.html +++ b/app/jobs/templates/jobs/sidebar_search.html @@ -24,23 +24,23 @@
              - - + +
              - - + +
              - - + +
              - - + +
              @@ -50,24 +50,24 @@
              {% trans "Employment Type" %}
              - +
              - +
              - +
              - +
              - +
              - +
              From db12ca6d7157e5f7762aafd56e805c4afad684dc Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Thu, 7 Jun 2018 00:11:05 +0530 Subject: [PATCH 12/32] Fix job listing css --- app/assets/v2/css/jobs.css | 97 +++++++++++++++++++++++++++---- app/jobs/templates/jobs/list.html | 23 ++++---- 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/app/assets/v2/css/jobs.css b/app/assets/v2/css/jobs.css index e9cf001e0af..22ef8462677 100644 --- a/app/assets/v2/css/jobs.css +++ b/app/assets/v2/css/jobs.css @@ -11,6 +11,70 @@ h4 { text-align: left; } +#filter { + color: #3e24fb; +} + +#matches { + font-family: 'Muli', sans-serif; + font-weight: 300; + padding-left: 10px; +} + +.title { + text-transform: capitalize; +} + +.title-row { + display: flex; + margin-top: 2em; + padding-bottom: 5px; + border-bottom: 2px solid #3E24FB; +} + +.body .row { + margin-left: 0px; +} + +.body .avatar { + max-height: 50px; + max-width: 50px; +} + +.job-row { + color: #000000; + text-decoration: none; + padding: 10px 20px 10px 4em; +} + +.job-row:hover { + background-color: #F9F9F9; +} + +.job-row .avatar-container { + text-align: right; + margin-top: auto; + margin-bottom: auto; +} + +.job-row img { + margin-top: 6px; + margin-right: 20px; +} + +.job-row .title { + margin-bottom: 2px; +} + +.job-company-detail { + text-align: right; +} + +.job-info { + top: 4px; + text-align: right; +} + .job-title { padding-top: 2em; } @@ -19,10 +83,6 @@ h4 { padding-bottom: 6em; } -.job-row { - padding: 1em; -} - #job-board-title { text-transform: uppercase; color: #00A55E; @@ -34,9 +94,9 @@ h4 { color: #717171; } -.job-details { - padding-top: 1em; - display: flex; +.job-detail { + margin-top: 10px; + margin-bottom: 10px; } .job-type { @@ -47,6 +107,7 @@ h4 { .job-location { flex-grow: 1; + color: #666666; } .flex-grow-8 { @@ -77,7 +138,13 @@ h4 { } .job-company { - flex-grow: 1; + margin-right: 5px; + font-weight: bold; + color: #666666; +} + +.job-skill { + color: #666666; } .job-tags { @@ -122,6 +189,16 @@ h4 { color: #6184AC; } -#company-avatar-img { - width: 2em; +@media (max-width: 768px) { + + .job-info { + top: 10px; + font-size: 10px; + text-align: left; + } + + .job-row { + padding-left: 1em; + padding-right: 2em; + } } diff --git a/app/jobs/templates/jobs/list.html b/app/jobs/templates/jobs/list.html index 6cc1bf50c40..ef5747a1f0e 100644 --- a/app/jobs/templates/jobs/list.html +++ b/app/jobs/templates/jobs/list.html @@ -49,7 +49,7 @@
              -
              +
              @@ -74,31 +74,28 @@

              {% trans "No results found." %}

              diff --git a/app/jobs/templates/jobs/list.html b/app/jobs/templates/jobs/list.html index ef5747a1f0e..badd765c3ef 100644 --- a/app/jobs/templates/jobs/list.html +++ b/app/jobs/templates/jobs/list.html @@ -61,11 +61,6 @@

              {% trans "No results found." %}

              {% trans "Please try another query" %}

              -

              - {% trans "or" %} -

              -

              - {% blocktrans %}save search - to be notified when results do appear.{% endblocktrans %} -

              From 9de99acdaf611f71ea84300049b6ddf9a1bafa2e Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Thu, 5 Jul 2018 17:30:17 +0530 Subject: [PATCH 24/32] Remove search button from job listing --- .../templates/shared/search_bar.html | 21 ++++++++++++------- app/jobs/templates/jobs/create_job.html | 2 +- app/jobs/templates/jobs/list.html | 2 +- app/jobs/views.py | 3 ++- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/dashboard/templates/shared/search_bar.html b/app/dashboard/templates/shared/search_bar.html index 9312f98f922..65ddad813ed 100644 --- a/app/dashboard/templates/shared/search_bar.html +++ b/app/dashboard/templates/shared/search_bar.html @@ -27,13 +27,17 @@ {% trans "Search" %}
              -
              {% trans "SORT" %}
              - + {% if jobs_board %} + + {% else %} +
              {% trans "SORT" %}
              + + {% endif %}
              @@ -41,7 +45,10 @@
              + {% if jobs_board %} + {% else %} {% trans "Save Search" %} + {% endif %}
              diff --git a/app/jobs/templates/jobs/create_job.html b/app/jobs/templates/jobs/create_job.html index fd33d54ac63..b52b76ec7c2 100644 --- a/app/jobs/templates/jobs/create_job.html +++ b/app/jobs/templates/jobs/create_job.html @@ -79,7 +79,7 @@
              - +
              {{ form.company.errors }}
              diff --git a/app/jobs/templates/jobs/list.html b/app/jobs/templates/jobs/list.html index badd765c3ef..f3aa2b752f3 100644 --- a/app/jobs/templates/jobs/list.html +++ b/app/jobs/templates/jobs/list.html @@ -42,7 +42,7 @@
              - {% include 'shared/current_balance.html' %}
              diff --git a/requirements/base.txt b/requirements/base.txt index f6028dfbd0b..c37038fea60 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -60,3 +60,4 @@ django-classy-tags==0.8.0 django-cookie-law==2.0.1 django-impersonate==1.3 pg_activity +bleach==1.5.0 From ffa5c52dff896b91b8b3635047734ce3733d5be0 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sun, 5 Aug 2018 17:03:58 +0530 Subject: [PATCH 29/32] minor code fixes --- app/assets/v2/js/pages/jobs.js | 15 --------------- app/jobs/templates/jobs/sidebar_search.html | 2 +- app/jobs/views.py | 4 ++-- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/app/assets/v2/js/pages/jobs.js b/app/assets/v2/js/pages/jobs.js index 7d5fe6b35cb..24269e02496 100644 --- a/app/assets/v2/js/pages/jobs.js +++ b/app/assets/v2/js/pages/jobs.js @@ -111,21 +111,6 @@ var set_filter_header = function() { var toggleAny = function(event) { if (!event) return; - // var key = event.target.name; - // var anyOption = $('input[name=' + key + '][value=any]'); - - // // Selects option 'any' when no filter is applied - // if ($('input[name=' + key + ']:checked').length === 0) { - // anyOption.prop('checked', true); - // return; - // } - // if (event.target.value === 'any') { - // // Deselect other filters when 'any' is selected - // $('input[name=' + key + '][value!=any]').prop('checked', false); - // } else { - // // Deselect option 'any' when another filter is selected - // anyOption.prop('checked', false); - // } }; var getFilters = function() { diff --git a/app/jobs/templates/jobs/sidebar_search.html b/app/jobs/templates/jobs/sidebar_search.html index 9d340da38b9..c292c0aa1a0 100644 --- a/app/jobs/templates/jobs/sidebar_search.html +++ b/app/jobs/templates/jobs/sidebar_search.html @@ -20,7 +20,7 @@
              - {% trans "DATE POSTED" %} + {% trans "DATE POSTED" %}
              diff --git a/app/jobs/views.py b/app/jobs/views.py index f7f50d6be3c..b93fad9cdbd 100644 --- a/app/jobs/views.py +++ b/app/jobs/views.py @@ -8,7 +8,7 @@ def list_jobs(request): - context = dict() + context = {} return TemplateResponse(request, 'jobs/list.html', context=context) @@ -21,7 +21,7 @@ def job_detail(request, pk): def create_job(request): - context = dict() + context = {} if request.method == 'POST': form = JobForm(user=request.user, data=request.POST) if form.is_valid(): From 83db70c853b59340a7ece8cf02237b33871cf5c6 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sun, 12 Aug 2018 15:06:28 +0530 Subject: [PATCH 30/32] Address reviews --- app/assets/v2/css/jobs.css | 4 +- app/assets/v2/js/pages/job_details.js | 2 +- app/assets/v2/js/pages/jobs.js | 32 +++++++-------- .../templates/shared/search_bar.html | 22 +++++----- app/dashboard/views.py | 2 +- app/jobs/admin.py | 1 + app/jobs/api.py | 2 +- app/jobs/filters.py | 2 +- app/jobs/migrations/0001_initial.py | 40 ------------------- app/jobs/models.py | 34 +++++++--------- app/jobs/serializers.py | 4 +- app/jobs/templates/jobs/detail.html | 4 +- 12 files changed, 54 insertions(+), 95 deletions(-) delete mode 100644 app/jobs/migrations/0001_initial.py diff --git a/app/assets/v2/css/jobs.css b/app/assets/v2/css/jobs.css index c2880a8e259..ee8d939a789 100644 --- a/app/assets/v2/css/jobs.css +++ b/app/assets/v2/css/jobs.css @@ -90,7 +90,9 @@ h4 { font-size: 3em; } -.company-name, #exp-lang, #job-post-duration { +.company-name, +#exp-lang, +#job-post-duration { color: #717171; } diff --git a/app/assets/v2/js/pages/job_details.js b/app/assets/v2/js/pages/job_details.js index 1e253bd5b6b..3c4eba35836 100644 --- a/app/assets/v2/js/pages/job_details.js +++ b/app/assets/v2/js/pages/job_details.js @@ -2,7 +2,7 @@ /* eslint no-redeclare: "warn" */ var build_detail_page = function(result) { - result['posted_at'] = timeDifference(new Date(), new Date(result['posted_at'])); + result['created_at'] = timeDifference(new Date(), new Date(result['created_at'])); var template = $.templates('#job_detail'); html = template.render(result); diff --git a/app/assets/v2/js/pages/jobs.js b/app/assets/v2/js/pages/jobs.js index 24269e02496..c6dc55023f8 100644 --- a/app/assets/v2/js/pages/jobs.js +++ b/app/assets/v2/js/pages/jobs.js @@ -66,14 +66,14 @@ var set_sidebar_defaults = function() { if (q) { keywords = decodeURIComponent(q).replace(/^,|\s|,\s*$/g, ''); - if (localStorage['keywords']) { + if (localStorage['jobs_keywords']) { keywords.split(',').forEach(function(v, k) { - if (localStorage['keywords'].indexOf(v) === -1) { - localStorage['keywords'] += ',' + v; + if (localStorage['jobs_keywords'].indexOf(v) === -1) { + localStorage['jobs_keywords'] += ',' + v; } }); } else { - localStorage['keywords'] = keywords; + localStorage['jobs_keywords'] = keywords; } window.history.replaceState(history.state, 'Issue Explorer | Gitcoin', '/explorer'); @@ -127,10 +127,10 @@ var getFilters = function() { }); } - if (localStorage['keywords']) { - localStorage['keywords'].split(',').forEach(function(v, k) { + if (localStorage['jobs_keywords']) { + localStorage['jobs_keywords'].split(',').forEach(function(v, k) { _filters.push('' + v + '' + - ''); + ''); }); } @@ -138,13 +138,13 @@ var getFilters = function() { }; var removeFilter = function(key, value) { - if (key !== 'keywords') { + if (key !== 'jobs_keywords') { $('input[name=' + key + '][value=' + value + ']').prop('checked', false); } else { - localStorage['keywords'] = localStorage['keywords'].replace(value, '').replace(',,', ','); + localStorage['jobs_keywords'] = localStorage['jobs_keywords'].replace(value, '').replace(',,', ','); // Removing the start and last comma to avoid empty element when splitting with comma - localStorage['keywords'] = localStorage['keywords'].replace(/^,|,\s*$/g, ''); + localStorage['jobs_keywords'] = localStorage['jobs_keywords'].replace(/^,|,\s*$/g, ''); } refreshjobs(); @@ -203,8 +203,8 @@ var get_search_URI = function() { } } - if (localStorage['keywords']) { - localStorage['keywords'].split(',').forEach(function(v, k) { + if (localStorage['jobs_keywords']) { + localStorage['jobs_keywords'].split(',').forEach(function(v, k) { keywords += v + ', '; }); } @@ -545,14 +545,14 @@ $(document).ready(function() { this.value = ''; if (!isTechStack) { - if (localStorage['keywords']) { - localStorage['keywords'] += ',' + ui.item.value; + if (localStorage['jobs_keywords']) { + localStorage['jobs_keywords'] += ',' + ui.item.value; } else { - localStorage['keywords'] += ui.item.value; + localStorage['jobs_keywords'] += ui.item.value; } $('.filter-tags').append('' + ui.item.value + '' + - ''); + ''); } return false; diff --git a/app/dashboard/templates/shared/search_bar.html b/app/dashboard/templates/shared/search_bar.html index 65ddad813ed..74736b43033 100644 --- a/app/dashboard/templates/shared/search_bar.html +++ b/app/dashboard/templates/shared/search_bar.html @@ -27,17 +27,17 @@ {% trans "Search" %}
              - {% if jobs_board %} - - {% else %} -
              {% trans "SORT" %}
              - - {% endif %} + {% if jobs_board %} + + {% else %} +
              {% trans "SORT" %}
              + + {% endif %}
              diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 9d3d8c338c4..d6197bd6528 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -964,7 +964,7 @@ def profile_keywords_helper(handle): """ profile = profile_helper(handle, True) - keywords = list() + keywords = [] for repo in profile.repos_data: language = repo.get('language') if repo.get('language') else '' _keywords = language.split(',') diff --git a/app/jobs/admin.py b/app/jobs/admin.py index 0f83a379e00..102aa62ad60 100644 --- a/app/jobs/admin.py +++ b/app/jobs/admin.py @@ -5,4 +5,5 @@ @admin.register(Job) class JobAdmin(admin.ModelAdmin): + """Define the Jobs administration layout.""" list_display = ['id', 'title', 'skills', 'is_active'] diff --git a/app/jobs/api.py b/app/jobs/api.py index 398ac3cea6f..52cc2ab56fb 100644 --- a/app/jobs/api.py +++ b/app/jobs/api.py @@ -10,4 +10,4 @@ class JobViewSet(mixins.ListModelMixin, queryset = models.Job.objects.filter(is_active=True) serializer_class = serializers.JobSerializer filter_backends = (filters.JobPostedFilter, filters.EmploymentTypeFilter,) - ordering = ('-posted_at', ) + ordering = ('-created_at', ) diff --git a/app/jobs/filters.py b/app/jobs/filters.py index 1acc82f3a4b..07d90c1cead 100644 --- a/app/jobs/filters.py +++ b/app/jobs/filters.py @@ -12,7 +12,7 @@ def filter_queryset(self, request, queryset, view): if job_posted: job_posted_days_ago = int(job_posted) job_posted = datetime.now() - timedelta(days=job_posted_days_ago) - return queryset.filter(posted_at__gte=job_posted) + return queryset.filter(created_at__gte=job_posted) return queryset diff --git a/app/jobs/migrations/0001_initial.py b/app/jobs/migrations/0001_initial.py deleted file mode 100644 index 01b64d10bc9..00000000000 --- a/app/jobs/migrations/0001_initial.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 2.0.5 on 2018-06-09 10:04 - -from django.db import migrations, models -import django.utils.timezone -import jobs.models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Job', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=200, verbose_name='Title')), - ('description', models.TextField(verbose_name='Description')), - ('github_profile_link', models.URLField(blank=True, verbose_name='Github Profile Link')), - ('location', models.CharField(blank=True, max_length=50, verbose_name='Location')), - ('job_type', models.CharField(choices=[('full_time', 'Full-Time'), ('part_time', 'Part-Time'), ('contract', 'Contract'), ('intern', 'Intern')], max_length=50, verbose_name='Job Type')), - ('apply_url', models.URLField(blank=True)), - ('is_active', models.BooleanField(default=False, verbose_name='Is this job active?')), - ('skills', models.CharField(blank=True, max_length=60, null=True, verbose_name='skill')), - ('expiry_date', models.DateTimeField(default=jobs.models.get_expiry_time, verbose_name='Expiry Date')), - ('company', models.CharField(blank=True, max_length=50, null=True, verbose_name='Company')), - ('apply_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Contact Email for Job')), - ('posted_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Posted At')), - ('posted_by_gitcoin_username', models.CharField(blank=True, max_length=50, null=True, verbose_name='Username of person who posted Job')), - ], - options={ - 'verbose_name': 'Job', - 'verbose_name_plural': 'Jobs', - 'db_table': 'job', - }, - ), - ] diff --git a/app/jobs/models.py b/app/jobs/models.py index 8e87c8e3951..1d6c491edfc 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -6,21 +6,21 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -# Create your models here. - -JOB_TYPE_CHOICES = ( - ('full_time', _('Full-Time')), - ('part_time', _('Part-Time')), - ('contract', _('Contract')), - ('intern', _('Intern')), -) +from economy.models import SuperModel def get_expiry_time(): return timezone.now() + timedelta(days=30) -class Job(models.Model): +class Job(SuperModel): + JOB_TYPE_CHOICES = ( + ('full_time', _('Full-Time')), + ('part_time', _('Part-Time')), + ('contract', _('Contract')), + ('intern', _('Intern')), + ) + title = models.CharField( verbose_name=_('Title'), max_length=200, null=False, blank=False ) @@ -37,26 +37,22 @@ class Job(models.Model): ) apply_url = models.URLField(null=False, blank=True) is_active = models.BooleanField( - verbose_name=_('Is this job active?'), default=False, - null=False, blank=True + verbose_name=_('Is this job active?'), default=False ) - skills = models.CharField( + skills = models.ArrayField(models.CharField( verbose_name=_('skill'), max_length=60, null=True, blank=True - ) + )) expiry_date = models.DateTimeField( _('Expiry Date'), null=False, blank=False, default=get_expiry_time ) company = models.CharField(_('Company'), max_length=50, null=True, blank=True) apply_email = models.EmailField(_('Contact Email for Job'), null=True, blank=True) - posted_at = models.DateTimeField(_('Posted At'), null=False, blank=False, default=timezone.now) - posted_by_gitcoin_username = models.CharField( - _('Username of person who posted Job'), max_length=50, null=True, blank=True - ) + posted_by = models.ForeignKey('users.User', null=False, blank=False, related_name='posted_jobs') @property def posted_by_user_profile_url(self): - if self.posted_by_gitcoin_username: - return reverse('profile', args=[self.posted_by_gitcoin_username]) + if self.posted_by: + return reverse('profile', args=[self.posted_by]) return None def get_absolute_url(self): diff --git a/app/jobs/serializers.py b/app/jobs/serializers.py index 3c7d74ee65e..7df751e55d6 100644 --- a/app/jobs/serializers.py +++ b/app/jobs/serializers.py @@ -12,7 +12,7 @@ class JobSerializer(serializers.ModelSerializer): job_type = serializers.CharField(source='get_job_type_display') def get_company_avatar(self, obj): - return reverse('org_avatar', args=[obj.posted_by_gitcoin_username]) + return reverse('org_avatar', args=[obj.posted_by]) class Meta: model = Job @@ -20,5 +20,5 @@ class Meta: 'id', 'title', 'description', 'github_profile_link', 'apply_url', 'is_active', 'skills', 'expiry_date', 'location', 'job_type', 'url', 'company', 'apply_email', 'posted_by_user_profile_url', - 'posted_by_gitcoin_username', 'company_avatar', 'posted_at' + 'posted_by', 'company_avatar', 'created_at' ) diff --git a/app/jobs/templates/jobs/detail.html b/app/jobs/templates/jobs/detail.html index 87938d2f9c6..35c359e7fae 100644 --- a/app/jobs/templates/jobs/detail.html +++ b/app/jobs/templates/jobs/detail.html @@ -54,7 +54,7 @@

              [[:title]]

              Company: [[:company]]
              Experience: [[:skills]]
              -
              Posted: [[:posted_at]]
              +
              Posted: [[:created_at]]
              Apply on Company Site @@ -84,7 +84,7 @@

              Description

              Gitcoin Profile [[if posted_by_user_profile_url]] - @[[:posted_by_gitcoin_username]] + @[[:posted_by]] [[else]] Not available [[/if]] From 5feec00fb0bcd09746ecfc07683a2ab0508ec341 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sun, 12 Aug 2018 15:49:31 +0530 Subject: [PATCH 31/32] Fix migrations --- app/app/settings.py | 1 - app/jobs/migrations/0001_initial.py | 44 +++++++++++++++++++++++++++++ app/jobs/models.py | 10 ++++--- 3 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 app/jobs/migrations/0001_initial.py diff --git a/app/app/settings.py b/app/app/settings.py index 2fcdc9fcc31..c8c933c91a6 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -93,7 +93,6 @@ 'external_bounties', 'dataviz', 'jobs', - 'ethos', 'impersonate', ] diff --git a/app/jobs/migrations/0001_initial.py b/app/jobs/migrations/0001_initial.py new file mode 100644 index 00000000000..2c29817b83b --- /dev/null +++ b/app/jobs/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 2.1 on 2018-08-12 10:16 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion +import economy.models +import jobs.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('dashboard', '0105_auto_20180812_1016'), + ] + + operations = [ + migrations.CreateModel( + name='Job', + 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)), + ('title', models.CharField(max_length=200, verbose_name='Title')), + ('description', models.TextField(verbose_name='Description')), + ('github_profile_link', models.URLField(blank=True, verbose_name='Github Profile Link')), + ('location', models.CharField(blank=True, max_length=50, verbose_name='Location')), + ('job_type', models.CharField(choices=[('full_time', 'Full-Time'), ('part_time', 'Part-Time'), ('contract', 'Contract'), ('intern', 'Intern')], max_length=50, verbose_name='Job Type')), + ('apply_url', models.URLField(blank=True)), + ('is_active', models.BooleanField(default=False, verbose_name='Is this job active?')), + ('skills', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=60, null=True), size=None)), + ('expiry_date', models.DateTimeField(default=jobs.models.get_expiry_time, verbose_name='Expiry Date')), + ('company', models.CharField(blank=True, max_length=50, null=True, verbose_name='Company')), + ('apply_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Contact Email for Job')), + ('posted_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='posted_jobs', to='dashboard.Profile')), + ], + options={ + 'verbose_name': 'Job', + 'verbose_name_plural': 'Jobs', + 'db_table': 'job', + }, + ), + ] diff --git a/app/jobs/models.py b/app/jobs/models.py index 1d6c491edfc..d84a26b671f 100644 --- a/app/jobs/models.py +++ b/app/jobs/models.py @@ -4,6 +4,7 @@ from django.db import models from django.urls import reverse from django.utils import timezone +from django.contrib.postgres.fields import ArrayField from django.utils.translation import ugettext_lazy as _ from economy.models import SuperModel @@ -39,15 +40,16 @@ class Job(SuperModel): is_active = models.BooleanField( verbose_name=_('Is this job active?'), default=False ) - skills = models.ArrayField(models.CharField( - verbose_name=_('skill'), max_length=60, null=True, blank=True - )) + skills = ArrayField(models.CharField(max_length=60, null=True, blank=True)) expiry_date = models.DateTimeField( _('Expiry Date'), null=False, blank=False, default=get_expiry_time ) company = models.CharField(_('Company'), max_length=50, null=True, blank=True) apply_email = models.EmailField(_('Contact Email for Job'), null=True, blank=True) - posted_by = models.ForeignKey('users.User', null=False, blank=False, related_name='posted_jobs') + posted_by = models.ForeignKey( + 'dashboard.Profile', null=False, blank=False, related_name='posted_jobs', + on_delete=models.CASCADE + ) @property def posted_by_user_profile_url(self): From 5ea25f1e296468b7ebc26eee9c4ec88ff2fda4f0 Mon Sep 17 00:00:00 2001 From: Sanyam Khurana Date: Sun, 12 Aug 2018 16:02:33 +0530 Subject: [PATCH 32/32] Fix migrations --- .../migrations/0105_auto_20180812_1031.py | 70 +++++++++++++++++++ app/jobs/migrations/0001_initial.py | 4 +- 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 app/dashboard/migrations/0105_auto_20180812_1031.py diff --git a/app/dashboard/migrations/0105_auto_20180812_1031.py b/app/dashboard/migrations/0105_auto_20180812_1031.py new file mode 100644 index 00000000000..d26c2986f3d --- /dev/null +++ b/app/dashboard/migrations/0105_auto_20180812_1031.py @@ -0,0 +1,70 @@ +# Generated by Django 2.1 on 2018-08-12 10:31 + +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dashboard', '0104_auto_20180802_1804'), + ] + + operations = [ + migrations.AlterField( + model_name='activity', + name='metadata', + field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), + ), + migrations.AlterField( + model_name='bounty', + name='github_issue_details', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, null=True), + ), + migrations.AlterField( + model_name='bounty', + name='metadata', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), + ), + migrations.AlterField( + model_name='bounty', + name='privacy_preferences', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), + ), + migrations.AlterField( + model_name='bountyfulfillment', + name='fulfiller_metadata', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), + ), + migrations.AlterField( + model_name='profile', + name='discord_repos', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, default=list, size=None), + ), + migrations.AlterField( + model_name='profile', + name='form_submission_records', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list), + ), + migrations.AlterField( + model_name='profile', + name='slack_repos', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, default=list, size=None), + ), + migrations.AlterField( + model_name='tip', + name='metadata', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), + ), + migrations.AlterField( + model_name='useraction', + name='location_data', + field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), + ), + migrations.AlterField( + model_name='useraction', + name='metadata', + field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), + ), + ] diff --git a/app/jobs/migrations/0001_initial.py b/app/jobs/migrations/0001_initial.py index 2c29817b83b..5a87aef91a2 100644 --- a/app/jobs/migrations/0001_initial.py +++ b/app/jobs/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1 on 2018-08-12 10:16 +# Generated by Django 2.1 on 2018-08-12 10:31 import django.contrib.postgres.fields from django.db import migrations, models @@ -12,7 +12,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('dashboard', '0105_auto_20180812_1016'), + ('dashboard', '0105_auto_20180812_1031'), ] operations = [