Skip to content
This repository
Browse code

Added support for listing jobs from a single employer, changed all li…

…nks from hasgeek.in to hasgeek.com, and other minor stylesheet changes.

Checked in from Leh, Ladakh, at ~3550 metres above sea level.
  • Loading branch information...
commit 0d6c98c6dcee7967a539f10594defdef57480373 1 parent 18e6fba
Kiran Jonnalagadda jace authored
2  README.md
Source Rendered
... ... @@ -1,7 +1,7 @@
1 1 HasGeek Job Board -- beta
2 2 =========================
3 3
4   -Code for HasGeek's job board at http://jobs.hasgeek.in/
  4 +Code for HasGeek's job board at http://jobs.hasgeek.com/
5 5
6 6 You are welcome to contribute a patch or use this code to run your own job
7 7 board under the terms of the BSD license, but please design your own UI.
22 admin.py
@@ -6,7 +6,10 @@
6 6
7 7 from app import app
8 8 from search import delete_from_index
9   -from models import JobPost, agelimit
  9 +from models import db, JobPost, agelimit
  10 +from utils import md5sum
  11 +
  12 +# --- Admin command-line utilities -------------------------------------------
10 13
11 14 @app.route('/admin/update-search/<key>')
12 15 def delete_index(key):
@@ -19,3 +22,20 @@ def delete_index(key):
19 22 content_type='text/plain; charset=utf-8')
20 23 else:
21 24 abort(403)
  25 +
  26 +
  27 +@app.route('/admin/update-md5sum/<key>')
  28 +def update_md5sum(key):
  29 + # This view is a hack. We need proper SQL migrations instead of this
  30 + if key == app.config['PERIODIC_KEY']:
  31 + for post in JobPost.query.all():
  32 + if not post.md5sum:
  33 + post.md5sum = md5sum(post.email)
  34 + db.session.commit()
  35 + return Response("Updated md5sum for all posts.\n",
  36 + content_type='text/plain; charset=utf-8')
  37 + else:
  38 + abort(403)
  39 +
  40 +# --- Admin views ------------------------------------------------------------
  41 +# LastUser integration pending
16 forms.py
... ... @@ -1,5 +1,6 @@
1 1 # -*- coding: utf-8 -*-
2 2
  3 +import re
3 4 from flask import g, request
4 5 from flaskext.wtf import Form, TextField, TextAreaField, RadioField, FileField, BooleanField
5 6 from flaskext.wtf import Required, Email, Length, URL, ValidationError
@@ -8,6 +9,9 @@
8 9 from uploads import process_image
9 10 from utils import simplify_text
10 11
  12 +QUOTES_RE = re.compile(ur'[\'"`‘’“”′″‴]+')
  13 +
  14 +
11 15 def optional_url(form, field):
12 16 """
13 17 Validate URL only if present.
@@ -73,8 +77,20 @@ def validate_company_logo(form, field):
73 77 raise ValidationError("Unknown file format")
74 78
75 79 def validate_job_headline(form, field):
  80 + # XXX: These validations belong in a config file or in the db, not here.
76 81 if simplify_text(field.data) == 'awesome coder wanted at awesome company':
77 82 raise ValidationError(u"Come on, write your own headline. You aren’t just another run-of-the-mill company, right?")
  83 + if 'awesome' in field.data.lower():
  84 + raise ValidationError(u'We’ve had a bit too much awesome around here lately. Got another adjective?')
  85 + if 'rockstar' in field.data.lower() or 'ninja' in field.data.lower():
  86 + raise ValidationError(u'Sorry, we can’t help with hiring rockstars or ninjas. Got another adjective?')
  87 + if 'urgent' in field.data.lower():
  88 + raise ValidationError(u'Sorry, we can’t help with urgent requirements. Geeks don’t grow on trees')
  89 +
  90 + def validate_job_location(form, field):
  91 + if QUOTES_RE.search(field.data) is not None:
  92 + raise ValidationError(u"Don’t use quotes in the location name")
  93 +
78 94
79 95 class ConfirmForm(Form):
80 96 terms_accepted = BooleanField("I accept the terms of service",
1  models.py
@@ -122,6 +122,7 @@ class JobPost(db.Model):
122 122 company_logo = db.Column(db.Unicode(255), nullable=True)
123 123 company_url = db.Column(db.Unicode(255), nullable=False, default='')
124 124 email = db.Column(db.Unicode(80), nullable=False)
  125 + md5sum = db.Column(db.String(32), nullable=True)
125 126
126 127 # Payment, audit and workflow fields
127 128 promocode = db.Column(db.String(40), nullable=True)
4 sass/_fonts.scss
... ... @@ -1,5 +1,5 @@
1   -$font-default: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
2   -$font-site-title: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  1 +$font-default: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
  2 +$font-site-title: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
3 3 $font-stickie-header: 'Walter Turncoat', sans-serif;
4 4 $font-stickie-date: 'Architects Daughter', sans-serif;
5 5 $font-stickie-location: 'Walter Turncoat', sans-serif;
49 sass/_layout.scss
@@ -14,10 +14,6 @@ header {
14 14 border-bottom: 1px solid $color-sheet-border;
15 15 @include single-box-shadow($color-shadow, -5px, 1px, 5px);
16 16
17   - form#search {
18   - float: right;
19   - padding-left: 1.2em;
20   - }
21 17 #sections {
22 18 li {
23 19 float: left;
@@ -461,31 +457,31 @@ footer {
461 457 }
462 458 }
463 459
464   -
465 460 #search {
  461 + float: right;
  462 + padding-left: 1.2em;
  463 + position: relative;
  464 +
466 465 input {
467 466 padding: 0 5px;
468 467 margin: 0;
469 468 font-size: 14px;
470   - float: left;
471 469 @include border-radius(0);
472 470 border: none;
473 471 }
474 472
475 473 input[type="text"] {
476 474 border: 1px solid $color-outline;
477   - border-right: 0;
478 475 font-family: $font-input;
479 476 background: white image_url('search.png') 4px center no-repeat;
480 477 padding-left: 20px;
481   - @include border-top-left-radius(3px);
482   - @include border-bottom-left-radius(3px);
483   - width: 15em;
  478 + padding-right: 43px;
  479 + @include border-radius(3px);
  480 + width: 12em;
484 481 height: 25px;
485 482 }
486 483
487 484 input[type="submit"] {
488   - height: 27px;
489 485 border: 1px solid $color-outline;
490 486 border-left: 0;
491 487 font-family: $font-button;
@@ -496,39 +492,18 @@ footer {
496 492 @include background-image(linear-gradient(#e6e6e6, #d6d6d6));
497 493 font-weight: bold;
498 494 color: #666;
  495 + width: 40px;
  496 + position: absolute;
  497 + right: 0;
  498 + top: 0;
  499 + bottom: 0;
499 500 &:hover {
500   - //text-shadow: 0 1px 1px rgba(0, 0, 0, 0.5);
501 501 background: #d6d6d6;
502 502 @include background-image(linear-gradient(#d6d6d6, #c6c6c6));
503   - //color: #fff;
504 503 }
505 504 }
506 505 }
507 506
508   -/*
509   -input[type=search], input.search {
510   - @include border-radius(100px);
511   - background: white image_url('search.png') 4px center no-repeat;
512   - padding: 0.1em 0 0.1em 20px;
513   - vertical-align: baseline;
514   - font-family: $font-input;
515   - width: 15em;
516   - border: 1px solid $color-outline;
517   - &:focus {
518   - outline: none;
519   - border: 1px solid $color-title-geek;
520   - }
521   -}
522   -
523   -button {
524   - @include fancy-button;
525   - @include fancy-button-colors-matte(#dddddd);
526   - font-size: 100%;
527   - font-weight: bold;
528   - font-family: $font-button;
529   -}
530   -*/
531   -
532 507
533 508 h1 {
534 509 font-size: 200%;
2  static/css/editor.css
... ... @@ -1,5 +1,5 @@
1 1 body {
2   - font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  2 + font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
3 3 font-size: 100%;
4 4 color: #444;
5 5 }
116 static/css/screen.css
@@ -269,7 +269,7 @@ button {
269 269
270 270 /* Background textures by Rüdiger Appel from http://www.3quarks.com/en/BackgroundPattern/ */
271 271 body {
272   - font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  272 + font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
273 273 background: url('/static/img/canvas.jpg?881083570');
274 274 }
275 275
@@ -285,16 +285,9 @@ header {
285 285 box-shadow: -5px 1px 5px rgba(0, 0, 0, 0.4);
286 286 }
287 287 header:after {
288   - content: "\0020";
289   - display: block;
290   - height: 0;
  288 + content: "";
  289 + display: table;
291 290 clear: both;
292   - overflow: hidden;
293   - visibility: hidden;
294   -}
295   -header form#search {
296   - float: right;
297   - padding-left: 1.2em;
298 291 }
299 292 header #sections li {
300 293 float: left;
@@ -331,7 +324,7 @@ header #pitch {
331 324 }
332 325
333 326 #site-title {
334   - font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  327 + font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
335 328 float: left;
336 329 padding-right: 0.5em;
337 330 font-size: 30px;
@@ -499,12 +492,9 @@ footer {
499 492 /* Form elements */
500 493 }
501 494 #sheet:after {
502   - content: "\0020";
503   - display: block;
504   - height: 0;
  495 + content: "";
  496 + display: table;
505 497 clear: both;
506   - overflow: hidden;
507   - visibility: hidden;
508 498 }
509 499 #sheet #company-info {
510 500 color: #816894;
@@ -519,12 +509,9 @@ footer {
519 509 border-top: 1px solid #e8e8e8;
520 510 }
521 511 #sheet .section:after {
522   - content: "\0020";
523   - display: block;
524   - height: 0;
  512 + content: "";
  513 + display: table;
525 514 clear: both;
526   - overflow: hidden;
527   - visibility: hidden;
528 515 }
529 516 #sheet .section.first {
530 517 border-top: 0;
@@ -685,12 +672,9 @@ footer {
685 672 margin-bottom: 1em;
686 673 }
687 674 #sheet div.field:after {
688   - content: "\0020";
689   - display: block;
690   - height: 0;
  675 + content: "";
  676 + display: table;
691 677 clear: both;
692   - overflow: hidden;
693   - visibility: hidden;
694 678 }
695 679 #sheet div.field .checkbox label {
696 680 padding-left: 0.5em;
@@ -775,7 +759,7 @@ footer {
775 759 margin-bottom: 1em;
776 760 }
777 761 #sheet input, #sheet textarea {
778   - font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  762 + font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
779 763 }
780 764 #sheet fieldset {
781 765 *zoom: 1;
@@ -783,12 +767,9 @@ footer {
783 767 margin-bottom: 1em;
784 768 }
785 769 #sheet fieldset:after {
786   - content: "\0020";
787   - display: block;
788   - height: 0;
  770 + content: "";
  771 + display: table;
789 772 clear: both;
790   - overflow: hidden;
791   - visibility: hidden;
792 773 }
793 774 #sheet textarea {
794 775 width: 90%;
@@ -826,7 +807,7 @@ footer {
826 807 #sheet input[type="submit"], #guide input[type="submit"] {
827 808 height: 27px;
828 809 border: 1px solid #b7b7b7;
829   - font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  810 + font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
830 811 font-size: 14px;
831 812 -moz-border-radius: 3px;
832 813 -webkit-border-radius: 3px;
@@ -840,6 +821,7 @@ footer {
840 821 background-image: -webkit-linear-gradient(#e6e6e6, #d6d6d6);
841 822 background-image: -moz-linear-gradient(#e6e6e6, #d6d6d6);
842 823 background-image: -o-linear-gradient(#e6e6e6, #d6d6d6);
  824 + background-image: -ms-linear-gradient(#e6e6e6, #d6d6d6);
843 825 background-image: linear-gradient(#e6e6e6, #d6d6d6);
844 826 font-weight: bold;
845 827 color: #444;
@@ -851,14 +833,19 @@ footer {
851 833 background-image: -webkit-linear-gradient(#d6d6d6, #c6c6c6);
852 834 background-image: -moz-linear-gradient(#d6d6d6, #c6c6c6);
853 835 background-image: -o-linear-gradient(#d6d6d6, #c6c6c6);
  836 + background-image: -ms-linear-gradient(#d6d6d6, #c6c6c6);
854 837 background-image: linear-gradient(#d6d6d6, #c6c6c6);
855 838 }
856 839
  840 +#search {
  841 + float: right;
  842 + padding-left: 1.2em;
  843 + position: relative;
  844 +}
857 845 #search input {
858 846 padding: 0 5px;
859 847 margin: 0;
860 848 font-size: 14px;
861   - float: left;
862 849 -moz-border-radius: 0;
863 850 -webkit-border-radius: 0;
864 851 -o-border-radius: 0;
@@ -869,30 +856,23 @@ footer {
869 856 }
870 857 #search input[type="text"] {
871 858 border: 1px solid #b7b7b7;
872   - border-right: 0;
873   - font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  859 + font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
874 860 background: white url('/static/img/search.png?1299763894') 4px center no-repeat;
875 861 padding-left: 20px;
876   - -moz-border-radius-topleft: 3px;
877   - -webkit-border-top-left-radius: 3px;
878   - -o-border-top-left-radius: 3px;
879   - -ms-border-top-left-radius: 3px;
880   - -khtml-border-top-left-radius: 3px;
881   - border-top-left-radius: 3px;
882   - -moz-border-radius-bottomleft: 3px;
883   - -webkit-border-bottom-left-radius: 3px;
884   - -o-border-bottom-left-radius: 3px;
885   - -ms-border-bottom-left-radius: 3px;
886   - -khtml-border-bottom-left-radius: 3px;
887   - border-bottom-left-radius: 3px;
888   - width: 15em;
  862 + padding-right: 43px;
  863 + -moz-border-radius: 3px;
  864 + -webkit-border-radius: 3px;
  865 + -o-border-radius: 3px;
  866 + -ms-border-radius: 3px;
  867 + -khtml-border-radius: 3px;
  868 + border-radius: 3px;
  869 + width: 12em;
889 870 height: 25px;
890 871 }
891 872 #search input[type="submit"] {
892   - height: 27px;
893 873 border: 1px solid #b7b7b7;
894 874 border-left: 0;
895   - font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  875 + font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
896 876 -moz-border-radius-topright: 3px;
897 877 -webkit-border-top-right-radius: 3px;
898 878 -o-border-top-right-radius: 3px;
@@ -911,9 +891,15 @@ footer {
911 891 background-image: -webkit-linear-gradient(#e6e6e6, #d6d6d6);
912 892 background-image: -moz-linear-gradient(#e6e6e6, #d6d6d6);
913 893 background-image: -o-linear-gradient(#e6e6e6, #d6d6d6);
  894 + background-image: -ms-linear-gradient(#e6e6e6, #d6d6d6);
914 895 background-image: linear-gradient(#e6e6e6, #d6d6d6);
915 896 font-weight: bold;
916 897 color: #666;
  898 + width: 40px;
  899 + position: absolute;
  900 + right: 0;
  901 + top: 0;
  902 + bottom: 0;
917 903 }
918 904 #search input[type="submit"]:hover {
919 905 background: #d6d6d6;
@@ -921,32 +907,10 @@ footer {
921 907 background-image: -webkit-linear-gradient(#d6d6d6, #c6c6c6);
922 908 background-image: -moz-linear-gradient(#d6d6d6, #c6c6c6);
923 909 background-image: -o-linear-gradient(#d6d6d6, #c6c6c6);
  910 + background-image: -ms-linear-gradient(#d6d6d6, #c6c6c6);
924 911 background-image: linear-gradient(#d6d6d6, #c6c6c6);
925 912 }
926 913
927   -/*
928   -input[type=search], input.search {
929   - @include border-radius(100px);
930   - background: white image_url('search.png') 4px center no-repeat;
931   - padding: 0.1em 0 0.1em 20px;
932   - vertical-align: baseline;
933   - font-family: $font-input;
934   - width: 15em;
935   - border: 1px solid $color-outline;
936   - &:focus {
937   - outline: none;
938   - border: 1px solid $color-title-geek;
939   - }
940   -}
941   -
942   -button {
943   - @include fancy-button;
944   - @include fancy-button-colors-matte(#dddddd);
945   - font-size: 100%;
946   - font-weight: bold;
947   - font-family: $font-button;
948   -}
949   -*/
950 914 h1 {
951 915 font-size: 200%;
952 916 color: #df5e0e;
@@ -1143,7 +1107,7 @@ header .stickie {
1143 1107 #newpost input[type="submit"] {
1144 1108 height: 27px;
1145 1109 border: 1px solid #b7b7b7;
1146   - font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, arial, sans-serif;
  1110 + font-family: ff-meta-web-pro-1, ff-meta-web-pro-2, MetaPlus, arial, sans-serif;
1147 1111 font-size: 14px;
1148 1112 -moz-border-radius: 3px;
1149 1113 -webkit-border-radius: 3px;
@@ -1157,6 +1121,7 @@ header .stickie {
1157 1121 background-image: -webkit-linear-gradient(#e6e6e6, #d6d6d6);
1158 1122 background-image: -moz-linear-gradient(#e6e6e6, #d6d6d6);
1159 1123 background-image: -o-linear-gradient(#e6e6e6, #d6d6d6);
  1124 + background-image: -ms-linear-gradient(#e6e6e6, #d6d6d6);
1160 1125 background-image: linear-gradient(#e6e6e6, #d6d6d6);
1161 1126 font-weight: bold;
1162 1127 color: #444;
@@ -1167,6 +1132,7 @@ header .stickie {
1167 1132 background-image: -webkit-linear-gradient(#d6d6d6, #c6c6c6);
1168 1133 background-image: -moz-linear-gradient(#d6d6d6, #c6c6c6);
1169 1134 background-image: -o-linear-gradient(#d6d6d6, #c6c6c6);
  1135 + background-image: -ms-linear-gradient(#d6d6d6, #c6c6c6);
1170 1136 background-image: linear-gradient(#d6d6d6, #c6c6c6);
1171 1137 }
1172 1138
2  static/opensearch.xml
@@ -3,7 +3,7 @@
3 3 <ShortName>HasGeek Job Search</ShortName>
4 4 <Description>Search for jobs at the HasGeek Job Board</Description>
5 5 <Url type="text/html" method="get"
6   - template="http://jobs.hasgeek.in/search?q={searchTerms}"/>
  6 + template="http://jobs.hasgeek.com/search?q={searchTerms}"/>
7 7 <Developer>HasGeek</Developer>
8 8 <Language>en-us</Language>
9 9 <OutputEncoding>UTF-8</OutputEncoding>
4 templates/confirm_email.md
Source Rendered
@@ -18,8 +18,8 @@ and you wish to withdraw it:
18 18 The [HasGeek Job Board][jb] is a service of [HasGeek][hg]. Write to us at
19 19 info@hasgeek.in if you have suggestions or questions on this service.
20 20
21   -[jb]: http://jobs.hasgeek.in
22   -[hg]: http://hasgeek.in
  21 +[jb]: http://jobs.hasgeek.com
  22 +[hg]: http://hasgeek.com
23 23
24 24 If you did not list a job, you may safely ignore this email and the listing
25 25 will be automatically removed.
3  templates/detail.html
@@ -42,6 +42,9 @@
42 42 <p id="post-meta">
43 43 <a href="{{ url_for('browse_by_type', slug=post.type.slug) }}">{{ post.type.title }}</a> /
44 44 <a href="{{ url_for('browse_by_category', slug=post.category.slug) }}">{{ post.category.title }}</a>
  45 + {%- if post.md5sum %}
  46 + / <a href="{{ url_for('browse_by_email', md5sum=post.md5sum) }}">All jobs at {{ post.company_name }}</a>
  47 + {% endif -%}
45 48 </p>
46 49 </div>
47 50 <div class="section columns">
4 templates/inc/layout.html
@@ -25,7 +25,7 @@
25 25 <link href="http://fonts.googleapis.com/css?family=Architects+Daughter" rel="stylesheet" type="text/css">
26 26
27 27 <!-- Stylesheets -->
28   - <link href="{{ url_for('static', filename='css/screen.css') }}?v=6" media="screen, projection" rel="stylesheet" type="text/css" />
  28 + <link href="{{ url_for('static', filename='css/screen.css') }}?v=7" media="screen, projection" rel="stylesheet" type="text/css" />
29 29 <link href="{{ url_for('static', filename='css/print.css') }}?v=1" media="print" rel="stylesheet" type="text/css" />
30 30 <!--[if IE]>
31 31 <link href="{{ url_for('static', filename='css/ie.css') }}?v=3" media="screen, projection" rel="stylesheet" type="text/css" />
@@ -65,7 +65,7 @@
65 65 <footer>
66 66 {% block footer %}
67 67 <p>
68   - The HasGeek Job Board is a service of <a href="http://hasgeek.in/">HasGeek</a>.
  68 + The HasGeek Job Board is a service of <a href="http://hasgeek.com/">HasGeek</a>.
69 69 Subscribe to <a href="{{ url_for('feed') }}">the feed</a> or follow
70 70 <a href="http://twitter.com/hasjob">@hasjob on Twitter</a>
71 71 to find out when new jobs are posted. Hosted by
13 templates/index.html
@@ -7,6 +7,7 @@
7 7 {% block pageheaders %}
8 8 {%- if jobtype %}<link rel="alternate" type="application/atom+xml" title="{{ jobtype.title }} – {{ config['SITE_TITLE']|e }}" href="{{ url_for('feed_by_type', slug=jobtype.slug) }}" />{% endif -%}
9 9 {%- if jobcategory %}<link rel="alternate" type="application/atom+xml" title="{{ jobcategory.title }} – {{ config['SITE_TITLE']|e }}" href="{{ url_for('feed_by_category', slug=jobcategory.slug) }}" />{% endif -%}
  10 +{%- if md5sum %}<link rel="alternate" type="application/atom+xml" title="Jobs at {{ employer_name }} – {{ config['SITE_TITLE'] }}" href="{{ url_for('feed_by_email', md5sum=md5sum) }}" />{% endif -%}
10 11 {% endblock %}
11 12
12 13 {% block header %}
@@ -23,10 +24,18 @@
23 24 <li id="filtered" class="stickie">
24 25 <a href="{{ url_for('index') }}">
25 26 <span class="filtered">
26   - You are seeing
  27 + You are viewing
27 28 {% if jobtype %}{{ jobtype.title.lower() }}{% endif %}
28 29 {% if jobcategory %}{{ jobcategory.title.lower() }}{% endif %}
29   - jobs. See all jobs?
  30 + jobs. View all jobs?
  31 + </span>
  32 + </a>
  33 + </li>
  34 + {%- elif md5sum %}
  35 + <li id="filtered" class="stickie">
  36 + <a href="{{ url_for('index') }}">
  37 + <span class="filtered">
  38 + You are viewing jobs at {{ employer_name }}. View all jobs?
30 39 </span>
31 40 </a>
32 41 </li>
4 templates/search.html
@@ -5,8 +5,8 @@
5 5 <li id="filtered" class="stickie">
6 6 <a href="{{ url_for('index') }}">
7 7 <span class="filtered">
8   - You are seeing search results for
9   - “{{ request.args['q'] }}”. See all jobs?
  8 + You are viewing search results for
  9 + “{{ request.args['q'] }}”. View all jobs?
10 10 </span>
11 11 </a>
12 12 </li>
17 utils.py
@@ -3,6 +3,7 @@
3 3 from random import randint
4 4 from uuid import uuid4
5 5 from base64 import b64encode
  6 +from hashlib import md5
6 7 from BeautifulSoup import BeautifulSoup, Comment
7 8
8 9 #: This code adapted from http://en.wikipedia.org/wiki/Base_36#Python%5FConversion%5FCode
@@ -41,6 +42,7 @@ def random_long_key():
41 42 return base36encode(randint(1000000000000000,
42 43 10000000000000000))
43 44
  45 +
44 46 def random_hash_key():
45 47 """
46 48 Returns a random key that is exactly five letters long.
@@ -51,6 +53,7 @@ def random_hash_key():
51 53 """
52 54 return ('0000' + base36encode(randint(0, 60466175)))[-5:] # 60466175 is 'zzzzz'
53 55
  56 +
54 57 def newid():
55 58 """
56 59 Return a new random id that is exactly 22 characters long.
@@ -58,6 +61,20 @@ def newid():
58 61 return b64encode(uuid4().bytes, altchars=',-').replace('=', '')
59 62
60 63
  64 +def md5sum(data):
  65 + """
  66 + Return md5sum of data as a 32-character string.
  67 +
  68 + >>> md5sum('random text')
  69 + 'd9b9bec3f4cc5482e7c5ef43143e563a'
  70 + >>> md5sum(u'random text')
  71 + 'd9b9bec3f4cc5482e7c5ef43143e563a'
  72 + >>> len(md5sum('random text'))
  73 + 32
  74 + """
  75 + return md5(data).hexdigest()
  76 +
  77 +
61 78 VALID_TAGS = {'strong': [],
62 79 'em': [],
63 80 'p': [],
34 views.py
@@ -16,7 +16,7 @@
16 16 from models import db, POSTSTATUS, JobPost, JobType, JobCategory, JobPostReport, ReportCode, unique_hash, agelimit
17 17 import forms
18 18 from uploads import uploaded_logos, process_image
19   -from utils import sanitize_html, scrubemail
  19 +from utils import sanitize_html, scrubemail, md5sum
20 20 from search import do_search
21 21
22 22 mail = Mail()
@@ -38,11 +38,16 @@ def getposts(basequery=None):
38 38 # --- Routes ------------------------------------------------------------------
39 39
40 40 @app.route('/')
41   -def index(basequery=None, type=None, category=None):
  41 +def index(basequery=None, type=None, category=None, md5sum=None):
42 42 now = datetime.utcnow()
43 43 posts = getposts(basequery)
  44 + if posts:
  45 + employer_name = posts[0].company_name
  46 + else:
  47 + employer_name = u'a single employer'
44 48 return render_template('index.html', posts=posts, now=now, newlimit=newlimit,
45   - jobtype=type, jobcategory=category)
  49 + jobtype=type, jobcategory=category, md5sum=md5sum,
  50 + employer_name=employer_name)
46 51
47 52
48 53 @app.route('/type/<slug>')
@@ -67,16 +72,28 @@ def browse_by_category(slug):
67 72 return index(basequery=basequery, category=ob)
68 73
69 74
  75 +@app.route('/by/<md5sum>')
  76 +def browse_by_email(md5sum):
  77 + if not md5sum:
  78 + abort(404)
  79 + basequery = JobPost.query.filter_by(md5sum=md5sum)
  80 + return index(basequery=basequery, md5sum=md5sum)
  81 +
  82 +
70 83 @app.route('/feed')
71   -def feed(basequery=None, type=None, category=None):
  84 +def feed(basequery=None, type=None, category=None, md5sum=md5sum):
72 85 title = "All jobs"
73 86 if type:
74 87 title = type.title
75 88 elif category:
76 89 title = category.title
  90 + elif md5sum:
  91 + title = u"Jobs at a single employer"
77 92 posts = list(getposts(basequery))
78 93 if posts: # Can't do this unless posts is a list
79 94 updated = posts[0].datetime.isoformat()+'Z'
  95 + if md5sum:
  96 + title = posts[0].company_name
80 97 else:
81 98 updated = datetime.utcnow().isoformat()+'Z'
82 99 return Response(render_template('feed.xml', posts=posts, updated=updated, title=title),
@@ -105,6 +122,14 @@ def feed_by_category(slug):
105 122 return feed(basequery=basequery, category=ob)
106 123
107 124
  125 +@app.route('/by/<md5sum>/feed')
  126 +def feed_by_email(md5sum):
  127 + if not md5sum:
  128 + abort(404)
  129 + basequery = JobPost.query.filter_by(md5sum=md5sum)
  130 + return feed(basequery=basequery, md5sum=md5sum)
  131 +
  132 +
108 133 @app.route('/robots.txt')
109 134 def robots():
110 135 return Response("Disallow: /edit/*\n"
@@ -295,6 +320,7 @@ def editjob(hashid, key, form=None, post=None, validated=False):
295 320 post.company_name = form.company_name.data
296 321 post.company_url = form.company_url.data
297 322 post.email = form.poster_email.data
  323 + post.md5sum = md5sum(post.email)
298 324
299 325 # TODO: Provide option of replacing logo or leaving it alone
300 326 if request.files['company_logo']:
36 website.py
@@ -40,29 +40,29 @@
40 40 with app.test_request_context():
41 41 if models.JobType.query.count() == 0:
42 42 print >> sys.stderr, "Adding some job types"
43   - db.session.add(models.JobType(seq=10, slug='fulltime', title='Full-time employment'))
44   - db.session.add(models.JobType(seq=20, slug='contract', title='Short-term contract'))
45   - db.session.add(models.JobType(seq=30, slug='freelance', title='Freelance or consulting'))
46   - db.session.add(models.JobType(seq=40, slug='volunteer', title='Volunteer contributor'))
47   - db.session.add(models.JobType(seq=50, slug='partner', title='Partner for a venture'))
  43 + db.session.add(models.JobType(seq=10, slug='fulltime', title=u'Full-time employment'))
  44 + db.session.add(models.JobType(seq=20, slug='contract', title=u'Short-term contract'))
  45 + db.session.add(models.JobType(seq=30, slug='freelance', title=u'Freelance or consulting'))
  46 + db.session.add(models.JobType(seq=40, slug='volunteer', title=u'Volunteer contributor'))
  47 + db.session.add(models.JobType(seq=50, slug='partner', title=u'Partner for a venture'))
48 48 db.session.commit()
49 49 if models.JobCategory.query.count() == 0:
50 50 print >> sys.stderr, "Adding some job categories"
51   - db.session.add(models.JobCategory(seq=10, slug='programming', title='Programming'))
52   - db.session.add(models.JobCategory(seq=20, slug='ux', title='Interaction Design'))
53   - db.session.add(models.JobCategory(seq=30, slug='design', title='Graphic Design'))
54   - db.session.add(models.JobCategory(seq=40, slug='testing', title='Testing'))
55   - db.session.add(models.JobCategory(seq=50, slug='sysadmin', title='Systems Administration'))
56   - db.session.add(models.JobCategory(seq=60, slug='business', title='Business/Management'))
57   - db.session.add(models.JobCategory(seq=70, slug='edit', title='Writer/Editor'))
58   - db.session.add(models.JobCategory(seq=80, slug='support', title='Customer Support'))
59   - db.session.add(models.JobCategory(seq=90, slug='mobile', title='Mobile (iPhone, Android, other)'))
  51 + db.session.add(models.JobCategory(seq=10, slug='programming', title=u'Programming'))
  52 + db.session.add(models.JobCategory(seq=20, slug='ux', title=u'Interaction Design'))
  53 + db.session.add(models.JobCategory(seq=30, slug='design', title=u'Graphic Design'))
  54 + db.session.add(models.JobCategory(seq=40, slug='testing', title=u'Testing'))
  55 + db.session.add(models.JobCategory(seq=50, slug='sysadmin', title=u'Systems Administration'))
  56 + db.session.add(models.JobCategory(seq=60, slug='business', title=u'Business/Management'))
  57 + db.session.add(models.JobCategory(seq=70, slug='edit', title=u'Writer/Editor'))
  58 + db.session.add(models.JobCategory(seq=80, slug='support', title=u'Customer Support'))
  59 + db.session.add(models.JobCategory(seq=90, slug='mobile', title=u'Mobile (iPhone, Android, other)'))
60 60 db.session.commit()
61 61 if models.ReportCode.query.count() == 0:
62 62 print >> sys.stderr, "Adding some report codes"
63   - db.session.add(models.ReportCode(seq=10, slug='spam', title='Spam, not a job listing'))
64   - db.session.add(models.ReportCode(seq=20, slug='fake', title='Appears to be a fake listing'))
65   - db.session.add(models.ReportCode(seq=30, slug='anon', title='Organization is not clearly identified'))
66   - db.session.add(models.ReportCode(seq=40, slug='unclear', title='Job position is not properly described'))
  63 + db.session.add(models.ReportCode(seq=10, slug='spam', title=u'Spam, not a job listing'))
  64 + db.session.add(models.ReportCode(seq=20, slug='fake', title=u'Appears to be a fake listing'))
  65 + db.session.add(models.ReportCode(seq=30, slug='anon', title=u'Organization is not clearly identified'))
  66 + db.session.add(models.ReportCode(seq=40, slug='unclear', title=u'Job position is not properly described'))
67 67 db.session.commit()
68 68 app.run(debug=True)

0 comments on commit 0d6c98c

Please sign in to comment.
Something went wrong with that request. Please try again.