mirror of
				https://github.com/fspc/workstand.git
				synced 2025-10-31 00:15:35 -04:00 
			
		
		
		
	Not sure why these aren't in master but here we go! (#34)
* Load webpack only in debug. * Second stab at buildfile. * Move button. * Add new line. * Linting. * Spelling mistake. * Clean up. * Use babel-polyfill. * Updated Configuration Files for Fresh Install
This commit is contained in:
		
							parent
							
								
									c202751d84
								
							
						
					
					
						commit
						960e107415
					
				| @ -4,6 +4,10 @@ services: | ||||
| before_install: | ||||
|   - docker build -t bcbc/workstand:production . | ||||
| after_success: | ||||
|   - bash -c 'echo $TRAVIS_BRANCH' | ||||
|   - docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"; | ||||
|   - docker push bcbc/workstand:production; | ||||
|   - export REPO=bcbc/workstand | ||||
|   - export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "production"; else echo $TRAVIS_BRANCH ; fi` | ||||
|   - docker build -f Dockerfile -t $REPO:$COMMIT . | ||||
|   - docker tag $REPO:$COMMIT $REPO:$TAG | ||||
|   - docker tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER | ||||
|   - docker push $REPO | ||||
							
								
								
									
										2
									
								
								bikeshop_project/.flake8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								bikeshop_project/.flake8
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| [flake8] | ||||
| max-line-length = 120 | ||||
| @ -1,8 +1,5 @@ | ||||
| import ContentAdd from 'material-ui/svg-icons/content/add'; | ||||
| import fetch from 'isomorphic-fetch'; | ||||
| import FloatingActionButton from 'material-ui/FloatingActionButton'; | ||||
| import moment from 'moment'; | ||||
| import { polyFill } from 'es6-promise'; | ||||
| import React from 'react'; | ||||
| import RaisedButton from 'material-ui/RaisedButton'; | ||||
| 
 | ||||
| @ -150,9 +147,6 @@ export default class SignIn extends React.Component { | ||||
|         </div> | ||||
|         <div className="mdl-grid"> | ||||
|           <SignedInList members={this.state.signedIn} /> | ||||
|           <FloatingActionButton href="/members/new/"> | ||||
|             <ContentAdd /> | ||||
|           </FloatingActionButton> | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import ContentAdd from 'material-ui/svg-icons/content/add'; | ||||
| import FloatingActionButton from 'material-ui/FloatingActionButton'; | ||||
| import _ from 'lodash'; | ||||
| import React, { PropTypes } from 'react'; | ||||
| import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; | ||||
| @ -50,6 +52,9 @@ export default class SignedInList extends React.Component { | ||||
|     return ( | ||||
|       <div className="mdl-cell mdl-cell--12-col"> | ||||
|         <h3>Members signed in</h3> | ||||
|         <FloatingActionButton href="/members/new/"> | ||||
|           <ContentAdd /> | ||||
|         </FloatingActionButton> | ||||
|         <Table selectable={false}> | ||||
|           <TableHeader adjustForCheckbox={false} displaySelectAll={false}> | ||||
|             <TableRow> | ||||
|  | ||||
| @ -1,9 +1,8 @@ | ||||
| import React from 'react'; | ||||
| import { polyFill } from 'es6-promise'; | ||||
| import fetch from 'isomorphic-fetch'; | ||||
| import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; | ||||
| import FlatButton from 'material-ui/FlatButton'; | ||||
| import { Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle } from 'material-ui/Toolbar'; | ||||
| import { Toolbar, ToolbarGroup} from 'material-ui/Toolbar'; | ||||
| import TextField from 'material-ui/TextField'; | ||||
| import RaisedButton from 'material-ui/RaisedButton'; | ||||
| 
 | ||||
|  | ||||
| @ -142,7 +142,7 @@ COMPRESS_PRECOMPILERS = ( | ||||
| WEBPACK_LOADER = { | ||||
|     'DEFAULT': { | ||||
|         'CACHE': False, | ||||
|         'BUNDLE_DIR_NAME': 'bundles/', # must end with slash | ||||
|         'BUNDLE_DIR_NAME': 'bundles/',  # must end with slash | ||||
|         'STATS_FILE': os.path.join(BASE_DIR, '../webpack-stats.json'), | ||||
|         'POLL_INTERVAL': 0.1, | ||||
|         'IGNORE': ['.+\.hot-update.js', '.+\.map'] | ||||
| @ -159,4 +159,4 @@ HAYSTACK_CONNECTIONS = { | ||||
| HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' | ||||
| 
 | ||||
| LOGIN_REDIRECT_URL = 'home' | ||||
| LOGIN_URL = 'login' | ||||
| LOGIN_URL = 'login' | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import os | ||||
| import sys | ||||
| from .base import * | ||||
| from .base import *  # noqa | ||||
| 
 | ||||
| 
 | ||||
| # SECURITY WARNING: keep the secret key used in production secret! | ||||
| @ -10,8 +11,9 @@ DEBUG = True | ||||
| 
 | ||||
| ALLOWED_HOSTS = [] | ||||
| 
 | ||||
| if 'test' in sys.argv or 'test_coverage' in sys.argv: #Covers regular testing and django-coverage | ||||
|     DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3' | ||||
| # Covers regular testing and django-coverage | ||||
| if 'test' in sys.argv or 'test_coverage' in sys.argv: | ||||
|     DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'  # noqa | ||||
| 
 | ||||
| LOGGING = { | ||||
|     'version': 1, | ||||
| @ -39,22 +41,16 @@ LOGGING = { | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| INSTALLED_APPS += [ | ||||
| INSTALLED_APPS += [  # noqa | ||||
|     'corsheaders', | ||||
|     # 'debug_toolbar' | ||||
| ] | ||||
| 
 | ||||
| MIDDLEWARE_CLASSES.insert(0, 'django.middleware.common.CommonMiddleware') | ||||
| MIDDLEWARE_CLASSES.insert(0, 'django.middleware.common.CommonMiddleware')  # noqa | ||||
| 
 | ||||
| # MIDDLEWARE_CLASSES += [ | ||||
| #     'debug_toolbar.middleware.DebugToolbarMiddleware' | ||||
| # ] | ||||
| 
 | ||||
| # Don't worry about IP addresses, just show the toolbar. | ||||
| DEBUG_TOOLBAR_CONFIG = { | ||||
|     'SHOW_TOOLBAR_CALLBACK': lambda *args: True | ||||
| } | ||||
| 
 | ||||
| CORS_ORIGIN_ALLOW_ALL = True | ||||
| 
 | ||||
| ALLOWED_HOSTS = ['workstand.docker'] | ||||
| ALLOWED_HOSTS = ['workstand.docker','localhost'] | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| from .base import * | ||||
| import os | ||||
| from .base import *  # noqa | ||||
| 
 | ||||
| 
 | ||||
| # SECURITY WARNING: keep the secret key used in production secret! | ||||
| @ -40,9 +41,9 @@ LOGGING = { | ||||
| WEBPACK_LOADER = { | ||||
|     'DEFAULT': { | ||||
|         'CACHE': True, | ||||
|         'BUNDLE_DIR_NAME': 'dist/', # must end with slash | ||||
|         'STATS_FILE': os.path.join(BASE_DIR, '../webpack-stats-prod.json'), | ||||
|         'BUNDLE_DIR_NAME': 'dist/',  # must end with slash | ||||
|         'STATS_FILE': os.path.join(BASE_DIR, '../webpack-stats-prod.json'),  # noqa | ||||
|         'POLL_INTERVAL': 0.1, | ||||
|         'IGNORE': ['.+\.hot-update.js', '.+\.map'] | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,4 @@ | ||||
| import csv | ||||
| import json | ||||
| import sys | ||||
| import os | ||||
| 
 | ||||
| import requests | ||||
| @ -12,7 +10,6 @@ from core.models import Membership, Payment | ||||
| from registration.models import Member | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| def email_generator(): | ||||
|     url = 'http://randomword.setgetgo.com/get.php' | ||||
|     local = [] | ||||
|  | ||||
| @ -6,6 +6,6 @@ admin.site.register([Membership, Payment]) | ||||
| 
 | ||||
| 
 | ||||
| @admin.register(Visit) | ||||
| class VistAdmin(admin.ModelAdmin): | ||||
| class VisitAdmin(admin.ModelAdmin): | ||||
|     ordering = ('created_at',) | ||||
|     list_display = ('member', 'purpose', 'created_at') | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import logging | ||||
| from django.forms import BooleanField, CharField, CheckboxInput, RadioSelect, ModelForm, TextInput, HiddenInput, ChoiceField | ||||
| from django.forms import BooleanField, CharField, CheckboxInput, RadioSelect, ModelForm, TextInput, HiddenInput | ||||
| 
 | ||||
| from registration.models import Member | ||||
| 
 | ||||
|  | ||||
| @ -62,7 +62,7 @@ class Visit(models.Model): | ||||
|         (DONATE, 'donate'), | ||||
|         (STAFF, 'staff'), | ||||
|     ) | ||||
|      | ||||
| 
 | ||||
|     member = models.ForeignKey( | ||||
|         'registration.Member', | ||||
|         on_delete=models.CASCADE | ||||
|  | ||||
| @ -58,3 +58,10 @@ | ||||
|     <div id="root"></div> | ||||
|     {% render_bundle 'signin' %} | ||||
| {% endblock %} | ||||
| 
 | ||||
| {% block scripts %} | ||||
|     {% render_bundle 'babelPolyfill' %} | ||||
|     {% if DEBUG %} | ||||
|         {% render_bundle 'webpack' %} | ||||
|     {% endif %} | ||||
| {% endblock %} | ||||
|  | ||||
| @ -1,3 +1 @@ | ||||
| from django.test import TestCase | ||||
| 
 | ||||
| # Create your tests here. | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import logging | ||||
| 
 | ||||
| from django.conf import settings | ||||
| from django.contrib import messages | ||||
| from django.core.urlresolvers import reverse | ||||
| from django.http import HttpResponseRedirect | ||||
| @ -18,13 +19,14 @@ logger = logging.getLogger(__name__) | ||||
| @method_decorator(login_required, name='dispatch') | ||||
| class DashboardView(View): | ||||
|     def get(self, request): | ||||
|         return TemplateResponse(request, 'dashboard.html') | ||||
|         return TemplateResponse(request, 'dashboard.html', context={'DEBUG': settings.DEBUG}) | ||||
| 
 | ||||
| 
 | ||||
| @method_decorator(login_required, name='dispatch') | ||||
| class NewMembershipView(TemplateView): | ||||
|     template_name = 'membership_form.html' | ||||
| 
 | ||||
|     def get(self, request, member_id): | ||||
|     def get(self, request, member_id, **kwargs): | ||||
|         membership_form = MembershipForm(initial=dict(member=member_id)) | ||||
|         payment_form = PaymentForm() | ||||
|         return self.render_to_response(dict(membership_form=membership_form, payment_form=payment_form)) | ||||
|  | ||||
| @ -4,14 +4,13 @@ | ||||
|   "description": "A membership management app for the BCBC.", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|   "build": "node_modules/.bin/webpack --config webpack.config.js --progress --colors", | ||||
|   "build-production": "node_modules/.bin/webpack --config webpack.prod.config.js --progress --colors", | ||||
|   "watch": "node server.js" | ||||
| }, | ||||
|     "build": "node_modules/.bin/webpack --config webpack.config.js --progress --colors", | ||||
|     "build-production": "node_modules/.bin/webpack --config webpack.prod.config.js --progress --colors", | ||||
|     "watch": "node server.js" | ||||
|   }, | ||||
|   "author": "", | ||||
|   "license": "ISC", | ||||
|   "dependencies": { | ||||
|     "es6-promise": "^3.2.1", | ||||
|     "isomorphic-fetch": "^2.2.1", | ||||
|     "material-ui": "^0.16.6", | ||||
|     "moment": "^2.13.0", | ||||
| @ -25,10 +24,15 @@ | ||||
|     "babel": "^6.5.2", | ||||
|     "babel-core": "^6.9.1", | ||||
|     "babel-loader": "^6.2.4", | ||||
|     "babel-polyfill": "^6.22.0", | ||||
|     "babel-preset-es2015": "^6.9.0", | ||||
|     "babel-preset-react": "^6.5.0", | ||||
|     "babel-preset-stage-0": "^6.5.0", | ||||
|     "css-loader": "^0.23.1", | ||||
|     "eslint": "^3.9.1", | ||||
|     "eslint-plugin-import": "^2.1.0", | ||||
|     "eslint-plugin-jsx-a11y": "^2.2.3", | ||||
|     "eslint-plugin-react": "^6.6.0", | ||||
|     "extract-text-webpack-plugin": "^1.0.1", | ||||
|     "i": "^0.3.5", | ||||
|     "node-sass": "^3.4.2", | ||||
| @ -40,10 +44,6 @@ | ||||
|     "sass-loader": "^3.2.0", | ||||
|     "style-loader": "^0.13.1", | ||||
|     "toolbox-loader": "0.0.3", | ||||
|     "webpack-dev-server": "^1.14.1", | ||||
|     "eslint": "^3.9.1", | ||||
|     "eslint-plugin-jsx-a11y": "^2.2.3", | ||||
|     "eslint-plugin-import": "^2.1.0", | ||||
|     "eslint-plugin-react": "^6.6.0" | ||||
|     "webpack-dev-server": "^1.14.1" | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -35,4 +35,4 @@ class CustomUserAdmin(UserAdmin): | ||||
| class MemberAdmin(admin.ModelAdmin): | ||||
|     list_display = ('get_full_name',) | ||||
|     ordering = ('last_name',) | ||||
|     search_fields = ('email', 'first_name', 'last_name') | ||||
|     search_fields = ('email', 'first_name', 'last_name') | ||||
|  | ||||
| @ -1,10 +1,11 @@ | ||||
| from django.forms import ModelForm, EmailInput, TextInput, DateInput, CheckboxSelectMultiple, CharField, CheckboxInput, BooleanField | ||||
| from django.forms import ModelForm, EmailInput, TextInput, DateInput, CheckboxInput, BooleanField | ||||
| from django.utils import timezone | ||||
| from registration.models import Member | ||||
| 
 | ||||
| 
 | ||||
| class MemberForm(ModelForm): | ||||
|     waiver_substitute = BooleanField(required=False, label='I have read and agree to the above terms & conditions.', widget=CheckboxInput(attrs={'class': 'mdl-checkbox__input'})) | ||||
|     waiver_substitute = BooleanField(required=False, label='I have read and agree to the above terms & conditions.', | ||||
|                                      widget=CheckboxInput(attrs={'class': 'mdl-checkbox__input'})) | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Member | ||||
|  | ||||
| @ -10,4 +10,4 @@ class MemberSerializer(ModelSerializer): | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Member | ||||
|         fields = ('first_name', 'last_name', 'email', 'id') | ||||
|         fields = ('first_name', 'last_name', 'email', 'id') | ||||
|  | ||||
| @ -8,10 +8,8 @@ from django.test import Client, TestCase | ||||
| 
 | ||||
| from core.models import Visit | ||||
| from model_mommy import mommy | ||||
| from copy import copy | ||||
| 
 | ||||
| from ..models import CustomUser, Member | ||||
| from ..views import MemberFormView | ||||
| 
 | ||||
| logger = logging.getLogger('bikeshop') | ||||
| 
 | ||||
| @ -37,7 +35,7 @@ class TestMemberFormView(TestCase): | ||||
|             'last_name': 'Last', | ||||
|             'post_code': 'H0H0H0', | ||||
|             } | ||||
|         response = c.post(url, data=member_data) | ||||
|         c.post(url, data=member_data) | ||||
|         new_member = Member.objects.get(first_name='First', last_name='Last') | ||||
|         self.assertTrue(new_member) | ||||
| 
 | ||||
|  | ||||
| @ -58,7 +58,8 @@ class MemberFormView(View): | ||||
| class MemberSearchView(View): | ||||
|     def get(self, request, query): | ||||
|         sqs = SearchQuerySet().models(Member).autocomplete(text=query)[:5] | ||||
|         results = [dict(name=result.object.get_full_name(), email=result.object.email, id=result.object.id) for result in sqs] | ||||
|         results = [dict(name=result.object.get_full_name(), email=result.object.email, id=result.object.id) | ||||
|                    for result in sqs] | ||||
| 
 | ||||
|         data = json.dumps(dict(results=results)) | ||||
| 
 | ||||
|  | ||||
| @ -1,38 +1,35 @@ | ||||
| var path = require("path") | ||||
| var webpack = require('webpack') | ||||
| var BundleTracker = require('webpack-bundle-tracker') | ||||
| 
 | ||||
| const path = require('path'); | ||||
| const autoprefixer = require('autoprefixer'); | ||||
| require('babel-polyfill'); | ||||
| 
 | ||||
| module.exports = { | ||||
|     context: __dirname, | ||||
|   context: __dirname, | ||||
| 
 | ||||
|     entry: { | ||||
|       signin: './assets/js/index', | ||||
|       members: './assets/js/members/index', | ||||
|     }, | ||||
|   entry: { | ||||
|     signin: './assets/js/index', | ||||
|     members: './assets/js/members/index', | ||||
|     babelPolyfill: 'babel-polyfill', | ||||
|   }, | ||||
| 
 | ||||
|     output: { | ||||
|         path: path.resolve('./assets/bundles/'), | ||||
|         filename: "[name]-[hash].js" | ||||
|     }, | ||||
|   output: { | ||||
|     path: path.resolve('./assets/bundles/'), | ||||
|     filename: '[name]-[hash].js', | ||||
|   }, | ||||
| 
 | ||||
|     plugins: [ | ||||
|          | ||||
|     ], // add all common plugins here
 | ||||
|   plugins: [ | ||||
| 
 | ||||
|     module: { | ||||
|         loaders: [ | ||||
|              | ||||
|         ] | ||||
|     }, | ||||
|   ], // add all common plugins here
 | ||||
| 
 | ||||
|     resolve: { | ||||
|         modulesDirectories: [ | ||||
|             'node_modules', | ||||
|             'bower_components' | ||||
|         ], | ||||
|         extensions: ['', '.js', '.jsx', '.scss'] | ||||
|     }, | ||||
|     postcss: [autoprefixer] | ||||
| } | ||||
|   module: { | ||||
|     loaders: [], | ||||
|   }, | ||||
| 
 | ||||
|   resolve: { | ||||
|     modulesDirectories: [ | ||||
|       'node_modules', | ||||
|       'bower_components', | ||||
|     ], | ||||
|     extensions: ['', '.js', '.jsx', '.scss'], | ||||
|   }, | ||||
|   postcss: [autoprefixer], | ||||
| }; | ||||
|  | ||||
| @ -1,45 +1,47 @@ | ||||
| var path = require("path") | ||||
| var webpack = require('webpack') | ||||
| var BundleTracker = require('webpack-bundle-tracker') | ||||
| const webpack = require('webpack'); | ||||
| const BundleTracker = require('webpack-bundle-tracker'); | ||||
| const ExtractTextPlugin = require('extract-text-webpack-plugin'); | ||||
| require('babel-polyfill'); | ||||
| 
 | ||||
| var config = require('./webpack.base.config.js') | ||||
| 
 | ||||
| const config = require('./webpack.base.config.js'); | ||||
| 
 | ||||
| // Use webpack dev server
 | ||||
| config.entry = { | ||||
|     webpack: [ | ||||
|         'webpack-dev-server/client?http://webpack.docker:3000', | ||||
|         'webpack/hot/only-dev-server', | ||||
|     ], | ||||
|     signin: './assets/js/index', | ||||
|     members: './assets/js/members/index', | ||||
| } | ||||
|   webpack: [ | ||||
|     'webpack-dev-server/client?http://localhost:3000', | ||||
|     'webpack/hot/only-dev-server', | ||||
|   ], | ||||
|   signin: './assets/js/index', | ||||
|   members: './assets/js/members/index', | ||||
|   babelPolyfill: 'babel-polyfill', | ||||
| }; | ||||
| 
 | ||||
| // override django's STATIC_URL for webpack bundles
 | ||||
| config.output.publicPath = 'http://webpack.docker:3000/assets/bundles/' | ||||
| config.output.publicPath = 'http://localhost:3000/assets/bundles/'; | ||||
| 
 | ||||
| config.devtool = 'eval-source-map'; | ||||
| 
 | ||||
| // Add HotModuleReplacementPlugin and BundleTracker plugins
 | ||||
| config.plugins = config.plugins.concat([ | ||||
|     new webpack.HotModuleReplacementPlugin(), | ||||
|     new webpack.NoErrorsPlugin(), | ||||
|     new BundleTracker({filename: './webpack-stats.json'}), | ||||
|     new ExtractTextPlugin('react-toolbox.css', {allChunks: true}), | ||||
| ]) | ||||
|   new webpack.HotModuleReplacementPlugin(), | ||||
|   new webpack.NoErrorsPlugin(), | ||||
|   new BundleTracker({ filename: './webpack-stats.json' }), | ||||
|   new ExtractTextPlugin('react-toolbox.css', { allChunks: true }), | ||||
| ]); | ||||
| 
 | ||||
| // Add a loader for JSX files with react-hot enabled
 | ||||
| config.module.loaders.push( | ||||
|     { | ||||
|         test: /\.jsx?$/, | ||||
|         exclude: /node_modules/, | ||||
|         loaders: ['react-hot','babel-loader'] | ||||
|   { | ||||
|     test: /\.jsx?$/, | ||||
|     exclude: /node_modules/, | ||||
|     loaders: ['react-hot', 'babel-loader'], | ||||
| 
 | ||||
|     }, | ||||
|     { | ||||
|         test: /(\.scss|\.css)$/, | ||||
|         loader: ExtractTextPlugin.extract('style', 'css?sourceMap&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass?sourceMap!toolbox') | ||||
|     } | ||||
| ) | ||||
|   }, | ||||
|   { | ||||
|     test: /(\.scss|\.css)$/, | ||||
|     loader: ExtractTextPlugin.extract('style', 'css?sourceMap&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass?sourceMap!toolbox'), | ||||
|   } | ||||
| ); | ||||
| 
 | ||||
| module.exports = config | ||||
| module.exports = config; | ||||
|  | ||||
| @ -9,7 +9,7 @@ services: | ||||
|       - "8000:8000" | ||||
|       - "62260:62260" | ||||
|     volumes: | ||||
|       - ./bikeshop_project:/code | ||||
|       - ./bikeshop_project:/code:rw | ||||
|   redis: | ||||
|     restart: always | ||||
|   db: | ||||
|  | ||||
| @ -2,3 +2,4 @@ | ||||
| -r testing.txt | ||||
| django-debug-toolbar | ||||
| django-cors-headers | ||||
| flake8 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user