diff --git a/accounts/forms.py b/accounts/forms.py index f9413978..bfe17b77 100755 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -36,11 +36,7 @@ class Meta: 'stream', 'gender', 'position', - 'bio' ) # Note that we didn't mention user field here. - widgets = { - 'bio': forms.Textarea(attrs={'cols': 80, 'rows': 20}), - } def save(self, user=None): user_profile = super(UserProfileForm, self).save(commit=False) diff --git a/accounts/models.py b/accounts/models.py index 33985e61..fcc3c224 100755 --- a/accounts/models.py +++ b/accounts/models.py @@ -15,15 +15,54 @@ def __str__(self): class UserProfile(models.Model): + branch_choices = ( + ('CS', 'CSE'), + ('IT', 'IT'), + ('CC', 'CCE'), + ('ME', 'MECHANICAL'), + ('CV', 'CIVIL'), + ('EC', 'ECE'), + ('EE', 'EE'), + ('CM', 'CHEMICAL') + ) + + year_choices = ( + (1, 'One'), + (2, 'Two'), + (3, 'Three'), + (4, 'Four'), + ) + + stream_choices = ( + ('BT', 'B.Tech'), + ('BH', 'B.Hons'), + ('BJ', 'BJMC'), + ('BS', 'BSc'), + ('BC', 'BCA') + ) + + gender_choices = ( + ('M', 'Male'), + ('F', 'Female'), + ('O', 'Other') + ) + + position_choices = ( + ('ST', 'Student'), + ('PR', 'Professor'), + ('TA', 'Teaching Assistant'), + ('CO', 'Company') + ) + user = models.OneToOneField(User, on_delete=models.CASCADE, null=False) ratings = models.IntegerField(null=True, default=0, blank=True) photo = models.ImageField(upload_to="profile_image", null=True, blank=True) - year = models.IntegerField(null=True, default=1, blank=True) - branch = models.CharField(max_length=20, default="Not Updated", blank=True, null=True) - stream = models.CharField(max_length=20, default="Not Updated", blank=True, null=True) - gender = models.CharField(max_length=20, default="Not Updated", blank=True, null=True) - position = models.CharField(max_length=20, default="Not Updated", blank=True, null=True) # Student or Teacher - bio = models.TextField() + year = models.IntegerField(null=True, default=1, blank=True, choices=year_choices) + branch = models.CharField(max_length=20, default="Not Updated", blank=True, null=True, choices=branch_choices) + stream = models.CharField(max_length=20, default="Not Updated", blank=True, null=True, choices=stream_choices) + gender = models.CharField(max_length=20, default="Not Updated", blank=True, null=True, choices=gender_choices) + position = models.CharField(max_length=20, default="Not Updated", blank=True, null=True, choices=position_choices) # Student or Teacher + bio = models.TextField(help_text="Add some information about yourself") follows = models.ManyToManyField('self', related_name='followers', symmetrical=False, blank=True) class Meta: diff --git a/accounts/static/accounts/css/style.css b/accounts/static/accounts/css/style.css index ef585ca1..58416940 100644 --- a/accounts/static/accounts/css/style.css +++ b/accounts/static/accounts/css/style.css @@ -4,10 +4,7 @@ body { -webkit-font-smoothing: antialiased; background: #E4E4E4; } -.first{ -/* background-color: #bab9bf;*/ -} .img-thumbnail{ height: 180px; width: 180px; @@ -29,9 +26,90 @@ body { .third{ margin-left: 100px; } -/*.border{ - border-color: #0094ff; -}*/ + .move{ margin-left: 35px; +} + +.hidden-form { + display: none; +} + +#skill-form { + padding: 10px 0 10px 0; +} + +#skill { + height: 70px; +} + +#profile-pic-save { + margin-top: 3.2em; + display: none; +} + +.delete-skill:hover { + cursor: pointer; +} + +#update-info-now { + display: none; + width: 90%; + height: 75%; + position: fixed; + z-index: 1; + left: 0; + top: 0; + overflow: auto; + background-color: #ffffff; + box-shadow: 1px 2px 7px 2px#111111; + margin: 20vh 0 0 5%; + padding: 20px; +} + +#modal-title { + height: 20%; + display: grid; + grid-template-columns: repeat(5, 1fr); +} + +#modal-title-1 { + grid-row: 1; + grid-column: 1 / 5; +} + +.grided { + display: grid; + grid-template-columns: repeat(5, 1fr); + grid-gap: 20px; +} + +#update-submit-button { + margin: auto; + grid-column: 5; + grid-row: 2; +} + +.skill-row { + display: flex; + flex-direction: row; +} + +.skill-title { + width: 80%; +} + +.skill-delete-form { + width: 20%; +} + +#spinner-profile-pic { + display: none; + width: 10em; +} + +#save-pic-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-gap: 20px; } \ No newline at end of file diff --git a/accounts/static/accounts/img/spinner.gif b/accounts/static/accounts/img/spinner.gif new file mode 100644 index 00000000..1b1015a0 Binary files /dev/null and b/accounts/static/accounts/img/spinner.gif differ diff --git a/accounts/static/accounts/js/ajaxWrapper.js b/accounts/static/accounts/js/ajaxWrapper.js new file mode 100644 index 00000000..7bcf9d04 --- /dev/null +++ b/accounts/static/accounts/js/ajaxWrapper.js @@ -0,0 +1,155 @@ +/* +MIT License + +Copyright (c) 2020 daniel muremwa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Visit: https://github.com/muremwa/read-urls-extension.git + +*/ + +const ajax = (() => { + const requestType = { + POST: 'POST', + GET: 'GET' + }; + const crossSiteHeader = { + name: 'Access-Control-Allow-Origin', + value: '*' + }; + const flat200 = (status) => { + return parseInt(status.toString().replace(/\B\d/g, '0')); + }; + const xhr = new XMLHttpRequest(); + const addHeaders = (headers, cross) => { + cross ? headers.push(crossSiteHeader) : void 0; + headers.forEach((header) => { + xhr.setRequestHeader(header.name, header.value); + }); + }; + function __superRequest__(options) { + // cookies? + options.sendCookies === true ? xhr.withCredentials = true : void 0; + // add headers if any + options.headers && options.headers.length > 0 ? addHeaders(options.headers, options.crosssite) : void 0; + // set response type + xhr.responseType = options.responseType; + // error handler + xhr.onerror = options.error; + // request complete + xhr.onload = (event) => { + if (flat200(xhr.status) === 200) { + options.success({ + status: xhr.status, + statusText: xhr.statusText, + response: xhr.response + }); + } + else { + options.error(event); + } + ; + }; + } + ; + /* + send a get request + */ + function _get_request_(options) { + // add search params if any + if (options.params) { + if (typeof options.url === 'string') { + options.url = new URL(options.url); + } + ; + if (!('searchParams' in options.url)) { + throw TypeError('The url passed is incorrect'); + } + ; + options.params.forEach((param) => { + options.url.searchParams.set(param.name, param.value); + }); + } + ; + xhr.open(requestType.GET, options.url); + // download progress + if (options.downloadprogress) { + xhr.onprogress = (event) => { + if (event.lengthComputable) { + options.downloadprogress(event.lengthComputable, event.loaded, event.total); + } + else { + options.downloadprogress(event.lengthComputable, event.loaded); + } + ; + }; + } + ; + // call generic options + __superRequest__(options); + // send request bro + xhr.send(); + } + ; + /* + send A post request + */ + function _post_request_(options) { + if (options.data && options.form) { + throw new Error('Both data and form are currently not supported'); + } + ; + // open the request + xhr.open(requestType.POST, options.url); + // data to be sent to back end? + let _data = ''; + if (options.data) { + const jsonHeader = { + name: 'Content-type', + value: 'application/json; charset=utf-8' + }; + options.headers ? options.headers.push(jsonHeader) : options.headers = [jsonHeader,]; + _data = JSON.stringify(options.data); + } + else if (options.form) { + _data = new FormData(options.form); + } + ; + // call generic options + __superRequest__(options); + // upload progress start? + options.uploadstart ? xhr.upload.onloadstart = options.uploadstart : void 0; + // upload done? + options.uploadend ? xhr.upload.onload = options.uploadend : void 0; + // upload progress error? + options.uploaderror ? xhr.upload.onerror = options.uploaderror : void 0; + // upload progress? + options.uploadprogress ? xhr.upload.onprogress = (event) => { + options.uploadprogress(event.loaded, event.total); + } : void 0; + // send request + xhr.send(_data); + } + ; + return { + get: (options) => _get_request_(options), + post: (options) => _post_request_(options), + }; +})(); \ No newline at end of file diff --git a/accounts/static/accounts/js/profile.js b/accounts/static/accounts/js/profile.js new file mode 100644 index 00000000..7e39d0f7 --- /dev/null +++ b/accounts/static/accounts/js/profile.js @@ -0,0 +1,303 @@ +/* + This function toggles the display status of a form + pass it a button that's used to toogle + the button should have 2 attributes + 1. 'data-form-id': id of the form to toogle display. + 2. 'data-og-text': the original text on the button. +*/ +function toogleForm (target) { + const formId = target.dataset.formId; + const form = document.getElementById(formId); + + if (form) { + if (form.style.display === 'none' || !form.style.display) { + form.style.display = 'block'; + target.innerText = 'close form'; + } else { + form.style.display = 'none'; + target.innerText = target.dataset.ogText; + }; + }; +}; + + +// add listener to buttons to show forms +[...document.getElementsByClassName('toggle-form')].forEach((button) => { + button.addEventListener('click', (event) => { + toogleForm(event.target); + }); +}); + + +/* + Profile pic upload +*/ +function uploadPic () { + document.getElementById('profile-pic').click(); +}; + +const previewWarning = document.getElementById('preview-warning'); + +document.getElementById('profile-pic').addEventListener('change', (event) => { + // preview new profile pic for the user + const reader = new FileReader(); + reader.onload = (event) => document.getElementById('user-profile-pic').src = event.target.result; + reader.readAsDataURL(event.target.files[0]); + + // choose another picture + document.getElementById('profile-pic-upload').innerText = 'choose another picture'; + // save button + document.getElementById('profile-pic-save').style.display = 'block'; + previewWarning.style.display = 'block'; +}); + + +const savePictureButton = document.getElementById('profile-pic-save'); + +savePictureButton.addEventListener('click', () => { + const picForm = document.forms['profile-pic-form']; + const spinnerImg = document.getElementById('spinner-profile-pic'); + spinnerImg.src = spinnerImg.dataset.src; + spinnerImg.style.display = 'block'; + + const profilePicOptions = { + url: picForm.action, + responseType: 'json', + error: () => { + document.getElementById('pic-save-error').style.display = ''; + previewWarning.style.display = 'none'; + spinnerImg.style.display = 'none'; + }, + success: () => { + previewWarning.style.display = 'none'; + savePictureButton.style.display = 'none'; + spinnerImg.style.display = 'none'; + }, + form: picForm + }; + + ajax.post(profilePicOptions); +}); + + +/* + update info now +*/ + +// open update-form +document.getElementById('update-info-btn').addEventListener('click', () => { + document.getElementById('update-info-now').style.display = 'block'; +}) + + +// send form details +const modalCloseButton = document.getElementById('modal-close-button'); +const infoForm = document.forms['update-info-form']; +const submitInfoButton = document.getElementById('info-submit-button'); +const modalCloseCallback = (event) => { + submitInfoButton.disabled = true; + document.getElementById(event.target.dataset.modalId).style.display = 'none'; +}; + +if (infoForm) { + infoForm.addEventListener('change', () => submitInfoButton.disabled = false); + + infoForm.addEventListener('submit', (event) => { + event.preventDefault(); + modalCloseButton.disabled = true; + const title = document.getElementById('modal-title-1'); + title.innerHTML = '
{% endif %}
+
- Upload Pic ++
+
+
+
+
+
+ |
|||||
- Update Info -- |
- |||||
- - Add - Skill -+ + + |
|||||
- Stream : {{ user.userprofile.stream }}+Stream : {{ user.userprofile.stream }} |
|||||
- Branch : {{ user.userprofile.branch }}+Branch : {{ user.userprofile.branch }} |
|||||
- Year : {{ user.userprofile.year }}+Year : {{ user.userprofile.year }} |
|||||
You haven't added any skills to your profile!
+ {% endif %}| {{ skill }} | -- |
| You haven't added any skills to your profile! | -