From e97c9ec55de8b1a2ba8b97885c69c9dd8154eeec Mon Sep 17 00:00:00 2001 From: Miguel Dorado Date: Thu, 31 Jan 2019 11:10:52 +0800 Subject: [PATCH 1/8] added license model, put license dynamically on cart page --- .../app/components/cart/cart.component.html | 2 +- themes/admin.py | 16 +++++++++++++- themes/migrations/0020_license.py | 22 +++++++++++++++++++ themes/migrations/0021_theme_license.py | 19 ++++++++++++++++ themes/models.py | 13 +++++++++++ themes/views.py | 7 +++--- 6 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 themes/migrations/0020_license.py create mode 100644 themes/migrations/0021_theme_license.py diff --git a/assets/src/app/components/cart/cart.component.html b/assets/src/app/components/cart/cart.component.html index c0beac6..e398c47 100644 --- a/assets/src/app/components/cart/cart.component.html +++ b/assets/src/app/components/cart/cart.component.html @@ -11,7 +11,7 @@

{{theme.name}}

{{theme.description}}

License Type:

-

1 Standard License

+

{{ theme.license.license }}

Change
diff --git a/themes/admin.py b/themes/admin.py index bac5908..4275afc 100644 --- a/themes/admin.py +++ b/themes/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin -from .models import ( UserDownloadLog, Theme, Review, Thumbnail, Screenshot, Browser, Category, Topic, Label) +from .models import ( UserDownloadLog, Theme, Review, Thumbnail, Screenshot, Browser, Category, Topic, Label, License) class UserDownloadLogAdmin(admin.ModelAdmin): @@ -119,6 +119,19 @@ class LabelAdmin(admin.ModelAdmin): ) +class LicenseAdmin(admin.ModelAdmin): + """license admin + """ + model = License + + list_display = ( + 'license', + 'date_created', + 'date_modified', + ) + + + admin.site.register(UserDownloadLog, UserDownloadLogAdmin) admin.site.register(Theme, ThemeAdmin) admin.site.register(Review, ReviewAdmin) @@ -128,3 +141,4 @@ class LabelAdmin(admin.ModelAdmin): admin.site.register(Category, CategoryAdmin) admin.site.register(Topic, TopicAdmin) admin.site.register(Label, LabelAdmin) +admin.site.register(License, LicenseAdmin) diff --git a/themes/migrations/0020_license.py b/themes/migrations/0020_license.py new file mode 100644 index 0000000..79c843e --- /dev/null +++ b/themes/migrations/0020_license.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.5 on 2019-01-31 02:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('themes', '0019_auto_20190108_0553'), + ] + + operations = [ + migrations.CreateModel( + name='License', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('license', models.CharField(max_length=100)), + ('date_created', models.DateField(auto_now_add=True)), + ('date_modified', models.DateField(auto_now=True)), + ], + ), + ] diff --git a/themes/migrations/0021_theme_license.py b/themes/migrations/0021_theme_license.py new file mode 100644 index 0000000..62df0c2 --- /dev/null +++ b/themes/migrations/0021_theme_license.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.5 on 2019-01-31 02:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('themes', '0020_license'), + ] + + operations = [ + migrations.AddField( + model_name='theme', + name='license', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='themes.License'), + ), + ] diff --git a/themes/models.py b/themes/models.py index 0f92374..e308705 100644 --- a/themes/models.py +++ b/themes/models.py @@ -48,6 +48,7 @@ class Theme(models.Model): category = models.ForeignKey('themes.Category', on_delete=models.CASCADE, blank=True) topic = models.ForeignKey('themes.Topic', on_delete=models.CASCADE, blank=True) labels = models.ManyToManyField('themes.Label', blank=True) + license = models.ForeignKey('themes.License', on_delete=models.CASCADE, blank=True, null=True) release_date = models.DateField(auto_now=False,auto_now_add=False, blank=True) date_modified = models.DateField(auto_now=True) @@ -145,5 +146,17 @@ def __str__(self): return f'{self.label,}' +class License(models.Model): + """license + """ + license = models.CharField(max_length=100) + + date_created = models.DateField(auto_now_add=True) + date_modified = models.DateField(auto_now=True) + + def __str__(self): + return f'{self.license,}' + + diff --git a/themes/views.py b/themes/views.py index 0cf25cc..12f9522 100644 --- a/themes/views.py +++ b/themes/views.py @@ -1,8 +1,8 @@ from django.shortcuts import render from django.core import serializers from rest_framework.views import APIView -from .models import (Theme, Thumbnail, Screenshot, Review, Browser, Category, Topic, Label, ) -from .serializers import (ThemeDetailSerializer, ThumbnailSerializer, CategorySerializer, TopicSerializer,) +from .models import (Theme, Thumbnail, Screenshot, Review, Browser, Category, Topic, Label, License) +from .serializers import (ThemeDetailSerializer, ThumbnailSerializer, CategorySerializer, TopicSerializer, LicenseSerializer) from rest_framework.permissions import AllowAny from rest_framework.response import Response from itertools import chain @@ -72,11 +72,12 @@ def get(self,*args,**kwargs): theme = Theme.objects.get(id=kwargs['id']) category = Category.objects.get(id=theme.category_id) thumbnail = Thumbnail.objects.get(theme_id=theme.id) + license = License.objects.get(id=theme.license_id) theme_s = ThemeDetailSerializer(theme).data theme_s['thumbnail'] = ThumbnailSerializer(thumbnail).data theme_s['category'] = CategorySerializer(category).data - + theme_s['license'] = LicenseSerializer(license).data return Response(theme_s, status=200) From 91ade5879a40d2bb00e7da6acd704e3f30831d68 Mon Sep 17 00:00:00 2001 From: Miguel Dorado Date: Thu, 31 Jan 2019 11:19:34 +0800 Subject: [PATCH 2/8] removed pdb in register --- themes/views.py | 2 -- users/views.py | 1 - 2 files changed, 3 deletions(-) diff --git a/themes/views.py b/themes/views.py index 12f9522..4fdc804 100644 --- a/themes/views.py +++ b/themes/views.py @@ -9,8 +9,6 @@ import json - - class ThemeFeed(APIView): """themes home """ diff --git a/users/views.py b/users/views.py index 302f992..9e9146c 100644 --- a/users/views.py +++ b/users/views.py @@ -40,7 +40,6 @@ class Register(APIView): permission_classes = (AllowAny,) def post(self,request,*args,**kwargs): - import pdb; pdb.set_trace() serializer = self.serializer_class( data=self.request.data) From 3cd172a1499dfbd6c8bbce1eeca988311448279e Mon Sep 17 00:00:00 2001 From: Miguel Dorado Date: Tue, 5 Feb 2019 09:48:56 +0800 Subject: [PATCH 3/8] search filter functional, autocomplete search implemented --- assets/package-lock.json | 51 +++++++++++++++++-- assets/package.json | 9 +++- assets/src/app/app.module.ts | 16 ++++-- .../pipes/category/category.pipe.spec.ts | 8 +++ .../commons/pipes/category/category.pipe.ts | 18 +++++++ .../app/commons/services/home/home.service.ts | 22 +++++++- .../app/components/home/home.component.html | 34 ++++++++++--- .../src/app/components/home/home.component.ts | 38 +++++++++++--- assets/src/assets/js/main_script.js | 12 ++--- themes/urls.py | 4 +- themes/views.py | 20 ++++++-- 11 files changed, 194 insertions(+), 38 deletions(-) create mode 100644 assets/src/app/commons/pipes/category/category.pipe.spec.ts create mode 100644 assets/src/app/commons/pipes/category/category.pipe.ts diff --git a/assets/package-lock.json b/assets/package-lock.json index 9174aff..449bf0b 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -1,6 +1,6 @@ { - "name": "swiftkind", - "version": "2.0.0", + "name": "angular", + "version": "0.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9,6 +9,22 @@ "resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz", "integrity": "sha1-oOfJWsdiTWQXtElLHmi/9pMWiiA=" }, + "angular-text-input-autocomplete": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/angular-text-input-autocomplete/-/angular-text-input-autocomplete-0.3.0.tgz", + "integrity": "sha512-0oZBc1YhPalu7gOHqW8nEnkslnR+hcpnB79qV63opZbga1c0pfL4/MeKjVxW6SBauLCFAVoO1Mka0kwT80DSyw==", + "requires": { + "textarea-caret": "^3.1.0", + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + } + } + }, "animate.css": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-3.7.0.tgz", @@ -34,6 +50,26 @@ "resolved": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz", "integrity": "sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU=" }, + "keyboardevent-key-polyfill": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keyboardevent-key-polyfill/-/keyboardevent-key-polyfill-1.1.0.tgz", + "integrity": "sha1-ijGdjkWhMXL8pWKGNy+QwdTHAUw=" + }, + "ng-select2": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/ng-select2/-/ng-select2-1.0.8.tgz", + "integrity": "sha512-cMonV4RMKmJGw4MSuTJumLDDaZao3r8slGu53GlrdyOSAEMsrjuHKeWQxMlcBFTAqiSwImugYPulfZSXPmlO2w==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + } + } + }, "popper.js": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.1.tgz", @@ -49,16 +85,21 @@ "resolved": "https://registry.npmjs.org/select2/-/select2-4.0.6-rc.1.tgz", "integrity": "sha1-qmwwOKfw8ukf+t448KIcFeGBMnY=", "requires": { - "almond": "0.3.3", - "jquery-mousewheel": "3.1.13" + "almond": "~0.3.1", + "jquery-mousewheel": "~3.1.13" } }, + "textarea-caret": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz", + "integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==" + }, "wowjs": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wowjs/-/wowjs-1.1.3.tgz", "integrity": "sha1-RA/Bu0x+iWhA7keXIpaitZB1rL0=", "requires": { - "animate.css": "3.7.0" + "animate.css": "^3.7.0" }, "dependencies": { "animate.css": { diff --git a/assets/package.json b/assets/package.json index f9bef8d..cd4cf6e 100644 --- a/assets/package.json +++ b/assets/package.json @@ -12,20 +12,27 @@ "private": true, "dependencies": { "@angular/animations": "~7.1.0", + "@angular/cdk": "~7.2.2", "@angular/common": "~7.1.0", "@angular/compiler": "~7.1.0", "@angular/core": "^7.0.0", "@angular/forms": "~7.1.0", + "@angular/material": "~7.2.2", "@angular/platform-browser": "~7.1.0", "@angular/platform-browser-dynamic": "~7.1.0", "@angular/router": "~7.1.0", + "@auth0/angular-jwt": "^2.1.0", "angular-jwt": "^0.1.10", + "angular-text-input-autocomplete": "^0.3.0", "angular2-jwt": "^0.2.3", "animate.css": "^3.7.0", "bootstrap": "^4.0.0", "core-js": "^2.5.4", "ionicons": "^2.0.1", "jquery": "^3.3.1", + "keyboardevent-key-polyfill": "^1.1.0", + "ng-select2": "^1.0.8", + "ng2-select2": "^1.0.0-beta.11", "popper.js": "^1.14.1", "retinajs": "^2.1.3", "rxjs": "~6.3.3", @@ -55,4 +62,4 @@ "tslint": "~5.11.0", "typescript": "~3.1.6" } -} +} \ No newline at end of file diff --git a/assets/src/app/app.module.ts b/assets/src/app/app.module.ts index 465bfab..1eda7f3 100644 --- a/assets/src/app/app.module.ts +++ b/assets/src/app/app.module.ts @@ -1,11 +1,13 @@ import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { HttpClientModule,HTTP_INTERCEPTORS } from '@angular/common/http'; import { RouterModule, Routes } from "@angular/router"; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -// import { MatGridModule } from '@angular/material'; +import { Select2Module } from 'ng2-select2'; +import { polyfill } from 'keyboardevent-key-polyfill'; +import { TextInputAutocompleteModule } from 'angular-text-input-autocomplete'; //Service import { TokenService } from './commons/services/interceptors/token.service'; @@ -20,6 +22,9 @@ import { CartComponent } from './components/cart/cart.component'; import { DetailsComponent } from './components/details/details.component'; import { AccountComponent } from './components/account/account.component'; +//Pipes +import { CategoryPipe } from './commons/pipes/category/category.pipe'; + //Routes const routes: Routes = [ { path: '', component: HomeComponent }, @@ -28,6 +33,8 @@ const routes: Routes = [ { path: 'account', component: AccountComponent } ] +polyfill(); + @NgModule({ declarations: [ AppComponent, @@ -35,6 +42,7 @@ const routes: Routes = [ CartComponent, DetailsComponent, AccountComponent, + CategoryPipe, ], imports: [ @@ -44,6 +52,7 @@ const routes: Routes = [ HttpClientModule, FormsModule, ReactiveFormsModule, + TextInputAutocompleteModule, RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'}), ], providers: [ @@ -53,6 +62,7 @@ const routes: Routes = [ multi: true } ], - bootstrap: [AppComponent] + bootstrap: [AppComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class AppModule { } \ No newline at end of file diff --git a/assets/src/app/commons/pipes/category/category.pipe.spec.ts b/assets/src/app/commons/pipes/category/category.pipe.spec.ts new file mode 100644 index 0000000..45b4d74 --- /dev/null +++ b/assets/src/app/commons/pipes/category/category.pipe.spec.ts @@ -0,0 +1,8 @@ +import { CategoryPipe } from './category.pipe'; + +describe('CategoryPipe', () => { + it('create an instance', () => { + const pipe = new CategoryPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/assets/src/app/commons/pipes/category/category.pipe.ts b/assets/src/app/commons/pipes/category/category.pipe.ts new file mode 100644 index 0000000..10ce382 --- /dev/null +++ b/assets/src/app/commons/pipes/category/category.pipe.ts @@ -0,0 +1,18 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'category' +}) +export class CategoryPipe implements PipeTransform { + + transform(category:any, args?: any): any { + if(args === undefined) return category; + + return category.filter( + cat => { + return cat.category__category.toLowerCase().includes(args.toLowerCase()); + } + ) + } + +} diff --git a/assets/src/app/commons/services/home/home.service.ts b/assets/src/app/commons/services/home/home.service.ts index ee2bccc..d9b62c2 100644 --- a/assets/src/app/commons/services/home/home.service.ts +++ b/assets/src/app/commons/services/home/home.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' @@ -7,7 +8,10 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; export class HomeService { httpHeaders = new HttpHeaders({'Content-type': 'application/json'}); - constructor(private http: HttpClient) { } + public categories; + constructor(private http: HttpClient) { + this.categories = this.getCategory(); + } getThemes(){ return this.http.get("http://localhost:8000/home/theme/", {headers: this.httpHeaders}) @@ -24,4 +28,20 @@ export class HomeService { ) } + getCategory(){ + return this.http.get("http://localhost:8000/home/theme/category/", {headers: this.httpHeaders}) + .toPromise() + .then( + response => { + return response; + } + ) + .catch( + error => { + return error; + } + ) + } + + } diff --git a/assets/src/app/components/home/home.component.html b/assets/src/app/components/home/home.component.html index 5968ea6..0310313 100644 --- a/assets/src/app/components/home/home.component.html +++ b/assets/src/app/components/home/home.component.html @@ -15,21 +15,41 @@

We created themes so everyone can finish projects on time ship
- + + + + + +
+
+

- +
- +
diff --git a/assets/src/app/components/home/home.component.ts b/assets/src/app/components/home/home.component.ts index 2078647..f37aaa3 100644 --- a/assets/src/app/components/home/home.component.ts +++ b/assets/src/app/components/home/home.component.ts @@ -1,40 +1,66 @@ import { Component, OnInit } from '@angular/core'; import { HomeService } from '../../commons/services/home/home.service'; import { Title } from '@angular/platform-browser'; +import { CategoryPipe } from '../../commons/pipes/category/category.pipe'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +const categories = ['Angular JS','E-Commerce','General','Bootstrap 4']; @Component({ selector: 'app-home', templateUrl: './home.component.html', - styleUrls: ['./home.component.css'] + styleUrls: ['./home.component.css'], + providers: [CategoryPipe,HomeService] + }) export class HomeComponent implements OnInit { themes; + category; + categories + searchCategory; + promise; baseUrl = "http://localhost:8000/media/"; + + constructor( private home: HomeService, - private title: Title) { - } + private title: Title, + private fb: FormBuilder) {} ngOnInit() { this.getThemesHome(); this.title.setTitle('Home - Marketplace'); + console.log(this.home.categories); } - + getThemesHome(){ this.home.getThemes() .then( response => { this.themes = response.data; - console.log(this.themes); + this.category = response.category return response; } ) .catch( error => { - console.log(error); return error; } ) } + findCategory(search: string){ + return categories.filter( + category => { + console.log(category); + return category.toLowerCase().includes(search.toLowerCase()); + } + + ); + } + + getChoice(choice: string){ + return `${choice}`; + } + } diff --git a/assets/src/assets/js/main_script.js b/assets/src/assets/js/main_script.js index f8387a0..988add8 100644 --- a/assets/src/assets/js/main_script.js +++ b/assets/src/assets/js/main_script.js @@ -1,7 +1,8 @@ $(document).ready(function() { - $('.multi-select').select2({ - placeholder: 'Search for names, categories or technologies' - }); + $('.multi-select').select2({ + placeholder: 'Search for categories' + }); + new WOW().init(); //unmasking the password @@ -9,11 +10,6 @@ $(document).ready(function() { $('.pw-masking').toggleClass('masked'); }); - // $('#pw-toggle-register').on('click', function() { - // console.log('clicked'); - // $('.pw-masking').toggleClass('masked'); - // }); - //toggling tooltips for passwords var inputMask = $('.input-pw'); inputMask.on('focus',function() { diff --git a/themes/urls.py b/themes/urls.py index 340a2f6..1873102 100644 --- a/themes/urls.py +++ b/themes/urls.py @@ -1,11 +1,9 @@ from django.urls import path, include from . import views -from rest_framework import routers - urlpatterns = [ path('theme/', views.ThemeFeed.as_view()), path('theme/details//', views.ThemeNameFilter.as_view()), path('theme/cart//', views.ThemeCart.as_view()), - + path('theme/category/',views.CategoryView.as_view()), ] \ No newline at end of file diff --git a/themes/views.py b/themes/views.py index 4fdc804..76dedfc 100644 --- a/themes/views.py +++ b/themes/views.py @@ -16,14 +16,16 @@ class ThemeFeed(APIView): permission_classes = (AllowAny,) def get(self,*args,**kwargs): - thumbnail = Thumbnail.objects.all() - + thumbnail = Thumbnail.objects.all().values() + category = Category.objects.all().values() + data = self.queryset.filter( id__in=thumbnail.values('theme_id') - ).values('id','name','rating','price','thumbnail__thumbnail') + ).values('id','name','rating','price','thumbnail__thumbnail','category__category') return Response({ 'data': list(data), + 'category': list(category), }, status=200) @@ -76,6 +78,16 @@ def get(self,*args,**kwargs): theme_s['thumbnail'] = ThumbnailSerializer(thumbnail).data theme_s['category'] = CategorySerializer(category).data theme_s['license'] = LicenseSerializer(license).data - + return Response(theme_s, status=200) + +class CategoryView(APIView): + """category + """ + permission_classes = (AllowAny,) + + def get(self,*args,**kwargs): + category = Category.objects.all().values('category') + + return Response({'category': list(category)}, status=200) From b8214ebeabeece7077b3a7d3a96bad12dd9e3554 Mon Sep 17 00:00:00 2001 From: Miguel Dorado Date: Tue, 5 Feb 2019 12:03:47 +0800 Subject: [PATCH 4/8] removed console.log functions --- .../src/app/components/home/home.component.ts | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/assets/src/app/components/home/home.component.ts b/assets/src/app/components/home/home.component.ts index f37aaa3..222f831 100644 --- a/assets/src/app/components/home/home.component.ts +++ b/assets/src/app/components/home/home.component.ts @@ -16,51 +16,46 @@ const categories = ['Angular JS','E-Commerce','General','Bootstrap 4']; export class HomeComponent implements OnInit { themes; category; - categories searchCategory; - promise; baseUrl = "http://localhost:8000/media/"; constructor( - private home: HomeService, - private title: Title, - private fb: FormBuilder) {} + private home: HomeService, + private title: Title) {} ngOnInit() { - this.getThemesHome(); - this.title.setTitle('Home - Marketplace'); - console.log(this.home.categories); + this.getThemesHome(); + this.title.setTitle('Home - Marketplace'); } getThemesHome(){ - this.home.getThemes() - .then( - response => { - this.themes = response.data; - this.category = response.category - return response; - } - ) - .catch( - error => { - return error; - } - ) + this.home.getThemes() + .then( + response => { + this.themes = response.data; + this.category = response.category + return response; + } + ) + .catch( + error => { + return error; + } + ) } - findCategory(search: string){ - return categories.filter( - category => { - console.log(category); - return category.toLowerCase().includes(search.toLowerCase()); - } + findCategory(search: string){ + return categories.filter( + category => { + return category.toLowerCase().includes(search.toLowerCase()); + } - ); + ); } getChoice(choice: string){ - return `${choice}`; + return `${choice}`; } } From fa35c2095cb3b8f31cd1e1507e1a1709e687d953 Mon Sep 17 00:00:00 2001 From: Miguel Dorado Date: Tue, 5 Feb 2019 17:56:10 +0800 Subject: [PATCH 5/8] added form for creating a review, installed plug in for star rating --- assets/package-lock.json | 15 +++++++++++++++ assets/package.json | 3 ++- assets/src/app/app.module.ts | 6 +++++- assets/src/app/commons/pipes/category.pipe.ts | 19 +++++++++++++++++++ .../services/details/details.service.ts | 4 ++++ .../components/details/details.component.css | 13 +++++++++++++ .../components/details/details.component.html | 19 +++++++++++++++++++ .../components/details/details.component.ts | 17 ++++++++++++++++- 8 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 assets/src/app/commons/pipes/category.pipe.ts diff --git a/assets/package-lock.json b/assets/package-lock.json index 449bf0b..b4a4a0e 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -4,6 +4,21 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@ng-bootstrap/ng-bootstrap": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-4.0.2.tgz", + "integrity": "sha512-SBsN8ORvj/WXpZGSyR2+CRkg6GCtax5+fsLKt9ImHKUVWwePVqRxiGlnxXqwNPHQ46vOdd7nDN9cwE7dfbGaAQ==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + } + } + }, "almond": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz", diff --git a/assets/package.json b/assets/package.json index cd4cf6e..3071aab 100644 --- a/assets/package.json +++ b/assets/package.json @@ -22,6 +22,7 @@ "@angular/platform-browser-dynamic": "~7.1.0", "@angular/router": "~7.1.0", "@auth0/angular-jwt": "^2.1.0", + "@ng-bootstrap/ng-bootstrap": "^4.0.2", "angular-jwt": "^0.1.10", "angular-text-input-autocomplete": "^0.3.0", "angular2-jwt": "^0.2.3", @@ -62,4 +63,4 @@ "tslint": "~5.11.0", "typescript": "~3.1.6" } -} \ No newline at end of file +} diff --git a/assets/src/app/app.module.ts b/assets/src/app/app.module.ts index 1eda7f3..bc2686f 100644 --- a/assets/src/app/app.module.ts +++ b/assets/src/app/app.module.ts @@ -5,9 +5,10 @@ import { RouterModule, Routes } from "@angular/router"; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { Select2Module } from 'ng2-select2'; import { polyfill } from 'keyboardevent-key-polyfill'; import { TextInputAutocompleteModule } from 'angular-text-input-autocomplete'; +import { NgbPaginationModule, NgbAlertModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; //Service import { TokenService } from './commons/services/interceptors/token.service'; @@ -53,6 +54,9 @@ polyfill(); FormsModule, ReactiveFormsModule, TextInputAutocompleteModule, + NgbPaginationModule, + NgbAlertModule, + NgbModule, RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'}), ], providers: [ diff --git a/assets/src/app/commons/pipes/category.pipe.ts b/assets/src/app/commons/pipes/category.pipe.ts new file mode 100644 index 0000000..6472dd0 --- /dev/null +++ b/assets/src/app/commons/pipes/category.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'category' +}) +export class CategoryPipe implements PipeTransform { + + transform(category:any, args?: any): any { + if(args === undefined) return category; + + return category.filter( + cat => { + console.log(cat.category__category.toLowerCase()); + return cat.category__category.toLowerCase().includes(args.toLowerCase()); + } + ) + } + +} diff --git a/assets/src/app/commons/services/details/details.service.ts b/assets/src/app/commons/services/details/details.service.ts index 0221280..e4ba533 100644 --- a/assets/src/app/commons/services/details/details.service.ts +++ b/assets/src/app/commons/services/details/details.service.ts @@ -24,4 +24,8 @@ export class DetailsService { } ); } + + // createReviewService(comment){ + // return this.http.post("http://localhost:8000/details/review/"+comment.theme_id+"/",) + // } } diff --git a/assets/src/app/components/details/details.component.css b/assets/src/app/components/details/details.component.css index e69de29..1e75ac0 100644 --- a/assets/src/app/components/details/details.component.css +++ b/assets/src/app/components/details/details.component.css @@ -0,0 +1,13 @@ +.star { + font-size: 1.5rem; + color: #b0c4de; +} +.filled { + color: #1e90ff; +} +.bad { + color: #deb0b0; +} +.filled.bad { + color: #ff1e1e; +} \ No newline at end of file diff --git a/assets/src/app/components/details/details.component.html b/assets/src/app/components/details/details.component.html index de06e0f..230a7b1 100644 --- a/assets/src/app/components/details/details.component.html +++ b/assets/src/app/components/details/details.component.html @@ -195,8 +195,27 @@

Screenshots

+ + +
+

Write a Review

+
+
+ + +
+
+ +

Reviews

diff --git a/assets/src/app/components/details/details.component.ts b/assets/src/app/components/details/details.component.ts index 796e70e..e47361a 100644 --- a/assets/src/app/components/details/details.component.ts +++ b/assets/src/app/components/details/details.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { DetailsService } from '../../commons/services/details/details.service'; import { Title } from '@angular/platform-browser'; +import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; @Component({ selector: 'app-details', @@ -13,11 +14,15 @@ export class DetailsComponent implements OnInit { theme; discount; dis_price; + reviews; + currentRate:number = 0; + maxRate:number = 5; constructor( private route: ActivatedRoute, private detailsService: DetailsService, - private title: Title) {} + private title: Title, + private fb: FormBuilder) {} ngOnInit() { this.route.paramMap.subscribe( @@ -26,6 +31,15 @@ export class DetailsComponent implements OnInit { } ); this.getThemeDetails(); + + this.reviews = this.fb.group({ + review : new FormControl('', Validators.required) + }); + + } + + get review(){ + return this.reviews.get('review'); } getThemeDetails(){ @@ -52,5 +66,6 @@ export class DetailsComponent implements OnInit { ) } + } From be30f84e881e1c22886a9757490a5ad18ffb7839 Mon Sep 17 00:00:00 2001 From: Miguel Dorado Date: Wed, 6 Feb 2019 14:47:05 +0800 Subject: [PATCH 6/8] can now rate and comment a theme --- .../services/details/details.service.ts | 17 ++++- .../components/details/details.component.html | 70 ++++++++++++++++--- .../components/details/details.component.ts | 47 +++++++++++-- details/__init__.py | 0 details/admin.py | 3 + details/apps.py | 5 ++ details/migrations/__init__.py | 0 details/models.py | 3 + details/serializers.py | 27 +++++++ details/tests.py | 3 + details/urls.py | 6 ++ details/views.py | 33 +++++++++ market/urls.py | 1 + themes/views.py | 2 - 14 files changed, 194 insertions(+), 23 deletions(-) create mode 100644 details/__init__.py create mode 100644 details/admin.py create mode 100644 details/apps.py create mode 100644 details/migrations/__init__.py create mode 100644 details/models.py create mode 100644 details/serializers.py create mode 100644 details/tests.py create mode 100644 details/urls.py create mode 100644 details/views.py diff --git a/assets/src/app/commons/services/details/details.service.ts b/assets/src/app/commons/services/details/details.service.ts index e4ba533..7762845 100644 --- a/assets/src/app/commons/services/details/details.service.ts +++ b/assets/src/app/commons/services/details/details.service.ts @@ -25,7 +25,18 @@ export class DetailsService { ); } - // createReviewService(comment){ - // return this.http.post("http://localhost:8000/details/review/"+comment.theme_id+"/",) - // } + createReviewService(comment){ + return this.http.post("http://localhost:8000/details/createReview/",comment) + .toPromise() + .then( + response => { + return response; + } + ) + .catch( + error => { + return error; + } + ); + } } diff --git a/assets/src/app/components/details/details.component.html b/assets/src/app/components/details/details.component.html index 230a7b1..4e1b40c 100644 --- a/assets/src/app/components/details/details.component.html +++ b/assets/src/app/components/details/details.component.html @@ -204,14 +204,14 @@

Screenshots

Write a Review

-
@@ -227,16 +227,64 @@

Reviews

- {{ comments.user__first_name }} {{ comments.user__last_name }} + + {{ comments.date_created }}
-
- - - - - -
+ +
diff --git a/assets/src/app/components/details/details.component.ts b/assets/src/app/components/details/details.component.ts index e47361a..6565c90 100644 --- a/assets/src/app/components/details/details.component.ts +++ b/assets/src/app/components/details/details.component.ts @@ -10,13 +10,15 @@ import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms' styleUrls: ['./details.component.css'] }) export class DetailsComponent implements OnInit { + currentRate:number = 0; + maxRate:number = 5; theme_id:number; theme; discount; dis_price; reviews; - currentRate:number = 0; - maxRate:number = 5; + content; + token; constructor( private route: ActivatedRoute, @@ -32,8 +34,10 @@ export class DetailsComponent implements OnInit { ); this.getThemeDetails(); + //forms for creating a review this.reviews = this.fb.group({ - review : new FormControl('', Validators.required) + review : new FormControl('', Validators.required), + rating : new FormControl('') }); } @@ -42,6 +46,11 @@ export class DetailsComponent implements OnInit { return this.reviews.get('review'); } + get rating(){ + return this.review.get('rating'); + } + + // get details of the theme getThemeDetails(){ this.detailsService.getThemeDetailsService(this.theme_id) .then( @@ -55,17 +64,41 @@ export class DetailsComponent implements OnInit { this.dis_price = this.theme.price - this.dis_price; this.dis_price = this.dis_price.toFixed(2); } - - - } + } ) - .catch( + .catch( error => { return error; } ) } + //create a review and rating for the theme + createReview(review){ + this.token = JSON.parse(localStorage.getItem('token')); + this.content = { + theme_id : this.theme_id, + token : this.token.token, + comment : review, + rating : this.currentRate + } + + console.log(this.content); + this.detailsService.createReviewService(this.content) + .then( + response => { + this.getThemeDetails(); + this.review.reset(); + this.currentRate =0 + } + ) + .catch( + error => { + return error; + } + ) + } + } diff --git a/details/__init__.py b/details/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/details/admin.py b/details/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/details/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/details/apps.py b/details/apps.py new file mode 100644 index 0000000..a9f7696 --- /dev/null +++ b/details/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class DetailsConfig(AppConfig): + name = 'details' diff --git a/details/migrations/__init__.py b/details/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/details/models.py b/details/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/details/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/details/serializers.py b/details/serializers.py new file mode 100644 index 0000000..218c5e3 --- /dev/null +++ b/details/serializers.py @@ -0,0 +1,27 @@ +from rest_framework import serializers +from themes.models import Review +from django.utils.translation import ugettext_lazy as _ + + +class ReviewSerializer(serializers.ModelSerializer): + """review + """ + class Meta: + model = Review + fields = ( + 'theme', + 'user', + 'comment', + 'rating', + ) + + def validate(self,data): + comment = data.values + + if not comment or comment == "\n\n\n": + msg = 'Please enter a comment' + + return data + + + diff --git a/details/tests.py b/details/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/details/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/details/urls.py b/details/urls.py new file mode 100644 index 0000000..5be5e0b --- /dev/null +++ b/details/urls.py @@ -0,0 +1,6 @@ +from django.urls import path, include +from . import views + +urlpatterns = [ + path('createReview/', views.CreateReview.as_view()), +] \ No newline at end of file diff --git a/details/views.py b/details/views.py new file mode 100644 index 0000000..88cd33f --- /dev/null +++ b/details/views.py @@ -0,0 +1,33 @@ +from django.shortcuts import render +from themes.models import Review, Theme +from users.models import User +from rest_framework.views import APIView +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from .serializers import ReviewSerializer +from rest_framework.authtoken.models import Token + + +class CreateReview(APIView): + """create a review on a post + """ + permission_classes = (AllowAny,) + + def post(self,request,*args,**kwargs): + token = self.request.data.get('token') + + self.request.data['user'] = Token.objects.get(key=token).user_id + self.request.data['theme'] = self.request.data.get('theme_id') + + serializer = ReviewSerializer( + data=self.request.data) + + serializer.is_valid(raise_exception=True) + serializer.save() + + return Response({'success': 'Review created!'}, status=200) + + + + + \ No newline at end of file diff --git a/market/urls.py b/market/urls.py index a4058d6..5fbe2e3 100644 --- a/market/urls.py +++ b/market/urls.py @@ -9,6 +9,7 @@ path('admin/', admin.site.urls), path('user/', include('users.urls')), path('home/', include('themes.urls')), + path('details/', include('details.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/themes/views.py b/themes/views.py index 76dedfc..f319a63 100644 --- a/themes/views.py +++ b/themes/views.py @@ -5,8 +5,6 @@ from .serializers import (ThemeDetailSerializer, ThumbnailSerializer, CategorySerializer, TopicSerializer, LicenseSerializer) from rest_framework.permissions import AllowAny from rest_framework.response import Response -from itertools import chain -import json class ThemeFeed(APIView): From a33d02e4ce3ffd9e509d34546430ecce23747008 Mon Sep 17 00:00:00 2001 From: Miguel Dorado Date: Wed, 6 Feb 2019 16:08:08 +0800 Subject: [PATCH 7/8] dynamic star rating, update star rating of the theme after every created review --- .../components/details/details.component.html | 47 +++++++++++++++++-- details/views.py | 40 +++++++++++++--- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/assets/src/app/components/details/details.component.html b/assets/src/app/components/details/details.component.html index 4e1b40c..c44bbd2 100644 --- a/assets/src/app/components/details/details.component.html +++ b/assets/src/app/components/details/details.component.html @@ -30,11 +30,48 @@
{{ theme.name }}

{{ theme.name }}

- - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(20 Downloads)

diff --git a/details/views.py b/details/views.py index 88cd33f..3b180f1 100644 --- a/details/views.py +++ b/details/views.py @@ -8,25 +8,53 @@ from rest_framework.authtoken.models import Token + class CreateReview(APIView): """create a review on a post """ permission_classes = (AllowAny,) - + sum_values = 0 + def post(self,request,*args,**kwargs): - token = self.request.data.get('token') + """ + """ + token = request.data.get('token') - self.request.data['user'] = Token.objects.get(key=token).user_id - self.request.data['theme'] = self.request.data.get('theme_id') + request.data['user'] = Token.objects.get(key=token).user_id + request.data['theme'] = request.data.get('theme_id') serializer = ReviewSerializer( - data=self.request.data) + data=request.data) + """save review after validation + """ serializer.is_valid(raise_exception=True) serializer.save() - + + """get average rating of the theme between the ratings of the reviews + """ + theme_rating = Theme.objects.get(id=request.data.get('theme_id')) + reviews_rating = Review.objects.filter(theme_id=request.data.get('theme_id')).values('rating') + list_values = list(reviews_rating) + average_rating = self.get_average_rating(list_values,'rating') + + """update theme with new rating + """ + theme_rating.rating = int(average_rating) + theme_rating.save() + return Response({'success': 'Review created!'}, status=200) + def get_average_rating(self,list_values,key): + for rating in list_values: + self.sum_values += rating[key] + return self.sum_values/len(list_values) + + + + + + From afb04be473f738f53a84e657c012da0802e5ccb7 Mon Sep 17 00:00:00 2001 From: Miguel Dorado Date: Thu, 7 Feb 2019 10:25:38 +0800 Subject: [PATCH 8/8] added comments for guiding code --- details/serializers.py | 2 +- details/views.py | 7 +++++-- themes/views.py | 24 +++++++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/details/serializers.py b/details/serializers.py index 218c5e3..97847f0 100644 --- a/details/serializers.py +++ b/details/serializers.py @@ -18,7 +18,7 @@ class Meta: def validate(self,data): comment = data.values - if not comment or comment == "\n\n\n": + if not comment: msg = 'Please enter a comment' return data diff --git a/details/views.py b/details/views.py index 3b180f1..052411a 100644 --- a/details/views.py +++ b/details/views.py @@ -13,7 +13,7 @@ class CreateReview(APIView): """create a review on a post """ permission_classes = (AllowAny,) - sum_values = 0 + def post(self,request,*args,**kwargs): """ @@ -45,9 +45,12 @@ def post(self,request,*args,**kwargs): return Response({'success': 'Review created!'}, status=200) + def get_average_rating(self,list_values,key): + sum_values = 0 + for rating in list_values: - self.sum_values += rating[key] + sum_values += rating[key] return self.sum_values/len(list_values) diff --git a/themes/views.py b/themes/views.py index f319a63..42e09b5 100644 --- a/themes/views.py +++ b/themes/views.py @@ -31,12 +31,25 @@ class ThemeNameFilter(APIView): """themes details """ permission_classes = (AllowAny,) + sum_values = 0 def get(self,*args,**kwargs): theme = Theme.objects.get(id=kwargs['id']) review = Review.objects.filter(theme_id=theme.id ).values('rating','comment','date_created','user__first_name','user__last_name') + + """get average rating of the theme between the ratings of the reviews + """ + list_rating = review.filter(theme_id=theme.id).values('rating') + average_rating = self.get_average_rating(list_rating,'rating') + + """update rating with new rating + """ + theme.rating = int(average_rating) + theme.save() + """query necessary data to be used + """ screenshot = Screenshot.objects.filter(theme_id=theme.id).values('image') browser = theme.browser.all().values('browser') label = theme.labels.all().values('label') @@ -44,11 +57,15 @@ def get(self,*args,**kwargs): category = Category.objects.get(id=theme.category_id) topic = Topic.objects.get(id=theme.topic_id) + """convert querysets to serializable data + """ browser_s = {'browser':list(browser)} screenshot_s = {'screenshot':list(screenshot)} label_s = {'label':list(label)} review_s = {'review':list(review)} + """put everything in one object + """ theme_s = ThemeDetailSerializer(theme).data theme_s['thumbnail'] = ThumbnailSerializer(thumbnail).data theme_s['category'] = CategorySerializer(category).data @@ -58,7 +75,12 @@ def get(self,*args,**kwargs): theme_s['label'] = label_s theme_s['review'] = review_s - return Response(theme_s, status=200) + return Response(theme_s, status=200) + + def get_average_rating(self,list_values,key): + for rating in list_values: + self.sum_values += rating[key] + return self.sum_values/len(list_values) class ThemeCart(APIView):