Overview
Similar to how Tailwind handles responsive design, styling elements on hover, focus, and more can be accomplished by prefixing utilities with the appropriate pseudo-class.
<form>
<input class="bg-gray-200 hover:bg-white hover:border-gray-300 focus:outline-none focus:bg-white focus:shadow-outline focus:border-gray-300 ...">
<button class="bg-teal-500 hover:bg-teal-600 focus:outline-none focus:shadow-outline ...">
Sign Up
</button>
</form>
Not all pseudo-class variants are enabled for all utilities by default due to file-size considerations, but we've tried our best to enable the most commonly used combinations out of the box.
For a complete list of which variants are enabled by default, see the reference table at the end of this page.
Using pseudo-class variants
Tailwind includes first-class support for styling elements on hover, focus, active, group-hover, and focus-within. If you need to target a pseudo-class that Tailwind doesn't support, you can extend the supported variants by writing a variant plugin.
Hover
Add the hover:
prefix to only apply a utility on hover.
<button class="bg-transparent hover:bg-blue-500 text-blue-700 hover:text-white...">
Hover me
</button>
You can control whether hover
variants are enabled for a utility in the variants
section of your tailwind.config.js
file:
// tailwind.config.js
module.exports = {
// ...
variants: {
backgroundColor: ['hover'],
},
}
Focus
Add the focus:
prefix to only apply a utility on focus.
<input class="bg-gray-200 focus:bg-white border-transparent focus:border-blue-400 ..." placeholder="Focus me">
You can control whether focus
variants are enabled for a utility in the variants
section of your tailwind.config.js
file:
// tailwind.config.js
module.exports = {
// ...
variants: {
backgroundColor: ['focus'],
},
}
Active
Add the active:
prefix to only apply a utility when an element is active.
<button class="bg-blue-500 active:bg-blue-700 text-white...">
Click me
</button>
You can control whether active
variants are enabled for a utility in the variants
section of your tailwind.config.js
file:
// tailwind.config.js
module.exports = {
// ...
variants: {
backgroundColor: ['active'],
},
}
Group-hover
If you need to style a child element when hovering over a specific parent element, add the .group
class to the parent element and add the group-hover:
prefix to the utility on the child element.
New Project
Create a new project from a variety of starting templates.

New Project

Create a new project from a variety of starting templates.

You can control whether group-hover
variants are enabled for a utility in the variants
section of your tailwind.config.js
file:
// tailwind.config.js
module.exports = {
// ...
variants: {
backgroundColor: ['group-hover'],
},
}
Focus-within
Note that focus-within is not supported in IE or Edge.
Add the focus-within:
prefix to only apply a utility when a child element has focus.
<form class="border-b-2 border-gray-400 focus-within:border-teal-500 ...">
<input class="..." placeholder="Jane Doe" ...>
<button class="...">
Sign Up
</button>
</form>
You can control whether focus-within
variants are enabled for a utility in the variants
section of your tailwind.config.js
file:
// tailwind.config.js
module.exports = {
// ...
variants: {
backgroundColor: ['focus-within'],
},
}
Combining with responsive prefixes
Pseudo-class variants are also responsive, meaning you can do things like change an element's hover style at different breakpoints for example.
To apply a pseudo-class variant at a specific breakpoint, add the responsive prefix first, before the pseudo-class prefix:
Generative variants for custom utilities
You can generate pseudo-class variants for your own custom utilities by wrapping them with the @variants
directive in your CSS:
/* Input: */
@variants group-hover, hover, focus {
.banana {
color: yellow;
}
}
/* Output: */
.banana {
color: yellow;
}
.group:hover group-hover\:banana {
color: yellow;
}
.hover\:banana:hover {
color: yellow;
}
.focus\:banana:focus {
color: yellow;
}
For more information, see the @variants directive documentation.
Creating custom variants
You can create your own variants for any pseudo-classes Tailwind doesn't include by default by writing a custom variant plugin.
For example, this simple plugin adds support for the disabled
pseudo-class variant:
// tailwind.config.js
module.exports = {
plugins: [
function({ addVariant, e }) {
addVariant('disabled', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.${e(`disabled${separator}${className}`)}:disabled`
})
})
}
]
}
Learn more about writing variant plugins in the variant plugin documentation.
Default variants reference
Due to file-size considerations, Tailwind does not generate active
, group-hover
, or focus-within
variants for any utilities by default.
To configure which variants are enabled for your project, see the configuring variants documentation.
| Core Plugin | Hover | Focus |
|---|---|---|
alignContent |
||
alignItems |
||
alignSelf |
||
appearance |
||
backgroundAttachment |
||
backgroundColor |
✓ | ✓ |
backgroundPosition |
||
backgroundRepeat |
||
backgroundSize |
||
borderCollapse |
||
borderColor |
✓ | ✓ |
borderRadius |
||
borderStyle |
||
borderWidth |
||
boxShadow |
✓ | ✓ |
cursor |
||
display |
||
fill |
||
flex |
||
flexDirection |
||
flexGrow |
||
flexShrink |
||
flexWrap |
||
float |
||
fontFamily |
||
fontSize |
||
fontSmoothing |
||
fontStyle |
||
fontWeight |
✓ | ✓ |
height |
||
inset |
||
justifyContent |
||
letterSpacing |
||
lineHeight |
||
listStylePosition |
||
listStyleType |
||
margin |
||
maxHeight |
||
maxWidth |
||
minHeight |
||
minWidth |
||
objectFit |
||
objectPosition |
||
opacity |
||
order |
||
outline |
✓ | |
overflow |
||
padding |
||
pointerEvents |
||
position |
||
resize |
||
stroke |
||
tableLayout |
||
textAlign |
||
textColor |
✓ | ✓ |
textDecoration |
✓ | ✓ |
textTransform |
||
userSelect |
||
verticalAlign |
||
visibility |
||
whitespace |
||
width |
||
wordBreak |
||
zIndex |


Simple tools to work with files, paths etc.
“Copy all files from one folder to another.
Delete the files at the file paths in the list.
Parse underscore(or hyphen)-separated font file names into family and style names.
Get individual parameters from the path of a font file.
Get individual parameters from the style name of a font.
Read pairs of glyph names from a simple text file.
Rename a file or folder, and save it with the new name.
A simple non-recursive walk function to collect files with a given extension.
encoding
fontinfo
Enter search terms or a module, class or function name.


In this project, you'll use generative adversarial networks to generate new images of faces.
You'll be using two datasets in this project:
Since the celebA dataset is complex and you're doing GANs in a project for the first time, we want you to test your neural network on MNIST before CelebA. Running the GANs on MNIST will allow you to see how well your model trains sooner.
If you're using FloydHub, set data_dir
to "/input" and use the FloydHub data ID "R5KrjnANiKVhLWAkpXhNBe".
data_dir = './data'
# FloydHub - Use with data ID "R5KrjnANiKVhLWAkpXhNBe"
#data_dir = '/input'
"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
import helper
helper.download_extract('mnist', data_dir)
helper.download_extract('celeba', data_dir)
show_n_images = 25
"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
%matplotlib inline
import os
from glob import glob
from matplotlib import pyplot
mnist_images = helper.get_batch(glob(os.path.join(data_dir, 'mnist/*.jpg'))[:show_n_images], 28, 28, 'L')
pyplot.imshow(helper.images_square_grid(mnist_images, 'L'), cmap='gray')
The CelebFaces Attributes Dataset (CelebA) dataset contains over 200,000 celebrity images with annotations. Since you're going to be generating faces, you won't need the annotations. You can view the first number of examples by changing show_n_images
.
show_n_images = 25
"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
mnist_images = helper.get_batch(glob(os.path.join(data_dir, 'img_align_celeba/*.jpg'))[:show_n_images], 28, 28, 'RGB')
pyplot.imshow(helper.images_square_grid(mnist_images, 'RGB'))
Since the project's main focus is on building the GANs, we'll preprocess the data for you. The values of the MNIST and CelebA dataset will be in the range of -0.5 to 0.5 of 28x28 dimensional images. The CelebA images will be cropped to remove parts of the image that don't include a face, then resized down to 28x28.

You'll build the components necessary to build a GANs by implementing the following functions below:
model_inputs
discriminator
generator
model_loss
model_opt
train
This will check to make sure you have the correct version of TensorFlow and access to a GPU
"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
from distutils.version import LooseVersion
import warnings
import tensorflow as tf
# Check TensorFlow Version
assert LooseVersion(tf.__version__) >= LooseVersion('1.0'), 'Please use TensorFlow version 1.0 or newer. You are using {}'.format(tf.__version__)
print('TensorFlow Version: {}'.format(tf.__version__))
# Check for a GPU
if not tf.test.gpu_device_name():
warnings.warn('No GPU found. Please use a GPU to train your neural network.')
else:
print('Default GPU Device: {}'.format(tf.test.gpu_device_name()))
Implement the model_inputs
function to create TF Placeholders for the Neural Network. It should create the following placeholders:
image_width
, image_height
, and image_channels
.z_dim
.Return the placeholders in the following the tuple (tensor of real input images, tensor of z data)
import problem_unittests as tests
def model_inputs(image_width, image_height, image_channels, z_dim):
"""
Create the model inputs
:param image_width: The input image width
:param image_height: The input image height
:param image_channels: The number of image channels
:param z_dim: The dimension of Z
:return: Tuple of (tensor of real input images, tensor of z data, learning rate)
"""
# TODO: Implement Function
#HWC
input_real = tf.placeholder(tf.float32, [None, image_height, image_width, image_channels], name='input_real')
input_z = tf.placeholder(tf.float32, [None, z_dim], name='input_z')
learning_rate = tf.placeholder(tf.float32, name='learning_rate')
return (input_real, input_z, learning_rate)
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_model_inputs(model_inputs)
Implement discriminator
to create a discriminator neural network that discriminates on images
. This function should be able to reuse the variabes in the neural network. Use tf.variable_scope
with a scope name of "discriminator" to allow the variables to be reused. The function should return a tuple of (tensor output of the generator, tensor logits of the generator).
# Shape during CNN: 28x28xCH (3 or 1) -> 14x14x64 -> 7x7x128 -> 4x4x256 -> 1
def discriminator(images, reuse=False):
"""
Create the discriminator network
:param image: Tensor of input image(s)
:param reuse: Boolean if the weights should be reused
:return: Tuple of (tensor output of the discriminator, tensor logits of the discriminator)
"""
alpha = 0.2
keep_prob = 0.7
# TODO: Implement Function
with tf.variable_scope('discriminator', reuse=reuse):
# Input layer is 28x28xCH (3 or 1) -> 14x14x64
x = tf.layers.conv2d(inputs=images, filters=64, kernel_size=(3,3), strides=2, activation=None,
padding='same', kernel_initializer=tf.contrib.layers.xavier_initializer())
# No batch normalization, better results
x = tf.maximum(alpha * x, x)
x = tf.nn.dropout(x, keep_prob=keep_prob)
# -> 7x7x128
x1 = tf.layers.conv2d(x, filters = 128, kernel_size=(3,3), strides=2, activation=None,
padding='same', kernel_initializer=tf.contrib.layers.xavier_initializer())
x1 = tf.layers.batch_normalization(x1, training=True)
x1 = tf.maximum(alpha * x1, x1)
x1 = tf.nn.dropout(x1, keep_prob=keep_prob)
# -> 4x4x256
x2 = tf.layers.conv2d(x1, filters=256, kernel_size=(3,3), strides=2, activation=None,
padding='same', kernel_initializer=tf.contrib.layers.xavier_initializer())
x2 = tf.layers.batch_normalization(x2, training=True)
x2 = tf.maximum(alpha * x2, x2)
x2 = tf.nn.dropout(x2, keep_prob=keep_prob)
# Reshape for final dense layer -> units = 1 for sigmoid
x3 = tf.reshape(x2, shape=(-1, 4*4*256))
logits = tf.layers.dense(inputs=x3, units=1)
out = tf.sigmoid(logits)
return (out, logits)
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_discriminator(discriminator, tf)
Implement generator
to generate an image using z
. This function should be able to reuse the variabes in the neural network. Use tf.variable_scope
with a scope name of "generator" to allow the variables to be reused. The function should return the generated 28 x 28 x out_channel_dim
images.
# Shape during CNN: 100 (z_dim) -> 4x4x512 -> 7x7x256 -> 14x14x128 -> 28x28xCH
def generator(z, out_channel_dim, is_train=True):
"""
Create the generator network
:param z: Input z
:param out_channel_dim: The number of channels in the output image
:param is_train: Boolean if generator is being used for training
:return: The tensor output of the generator
"""
alpha = 0.2
keep_prob = 0.7
with tf.variable_scope('generator', reuse = not is_train):
#1st block -> 4x4x512
# First fully connected layer
# From input (1D to 1D of 4*4*512)
x = tf.layers.dense(z, units=(4*4*512))
# Reshahe the fully connected result (HWC)
x1 = tf.reshape(x, shape=(-1,4,4,512))
# Batch Norm + Leaky ReLU
x1 = tf.layers.batch_normalization(x1, training=is_train)
x1 = tf.maximum(alpha * x1, x1)
x1 = tf.nn.dropout(x1, keep_prob=keep_prob)
#2nd block -> 8x8x256
# Strides 2 with padding 'same' give the output 8x8 (for the filters we explicitly set 256)
x2 = tf.layers.conv2d_transpose(x1, filters=256, kernel_size=(3,3), strides=2,
padding='same', activation=None,
kernel_initializer=tf.contrib.layers.xavier_initializer())
# Batch Norm + Leaky ReLU
x2 = tf.layers.batch_normalization(x2, training=is_train)
x2 = tf.maximum(alpha * x2, x2)
x2 = tf.nn.dropout(x2, keep_prob=keep_prob)
# Resize to 7x7x256
x2 = tf.image.resize_nearest_neighbor(images=x2, size=(7, 7))
#3rd block -> 14x14x128
# Strides 2 with padding 'same' give the output 14x14 (for the filters we explicitly set 128)
x3 = tf.layers.conv2d_transpose(x2, filters=128, kernel_size=(3,3), strides=2,
padding='same', activation=None,
kernel_initializer=tf.contrib.layers.xavier_initializer())
x3 = tf.layers.batch_normalization(x3, training=is_train)
x3 = tf.maximum(alpha * x3, x3)
x3 = tf.nn.dropout(x3, keep_prob=keep_prob)
#4th block -> 28x28xCH (no batch normalization and use tanh activation)
logits = tf.layers.conv2d_transpose(x3, filters=out_channel_dim, kernel_size=(3,3), strides=2,
padding='same', activation=None,
kernel_initializer=tf.contrib.layers.xavier_initializer())
# Output layer, 28x28xCH
out = tf.tanh(logits)
return out
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_generator(generator, tf)
Implement model_loss
to build the GANs for training and calculate the loss. The function should return a tuple of (discriminator loss, generator loss). Use the following functions you implemented:
discriminator(images, reuse=False)
generator(z, out_channel_dim, is_train=True)
def model_loss(input_real, input_z, out_channel_dim):
"""
Get the loss for the discriminator and generator
:param input_real: Images from the real dataset
:param input_z: Z input
:param out_channel_dim: The number of channels in the output image
:return: A tuple of (discriminator loss, generator loss)
"""
# TODO: Implement Function
g_out = generator(z=input_z, out_channel_dim=out_channel_dim, is_train=True)
d_out_real, d_logits_real = discriminator(images=input_real, reuse=False)
d_out_fake, d_logits_fake = discriminator(images=g_out, reuse=True)
d_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real,
labels=tf.ones_like(d_logits_real)))
d_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake,
labels=tf.zeros_like(d_logits_fake)))
g_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake,
labels=tf.ones_like(d_logits_fake)))
d_loss = d_loss_real + d_loss_fake
return (d_loss, g_loss)
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_model_loss(model_loss)
Implement model_opt
to create the optimization operations for the GANs. Use tf.trainable_variables
to get all the trainable variables. Filter the variables with names that are in the discriminator and generator scope names. The function should return a tuple of (discriminator training operation, generator training operation).
def model_opt(d_loss, g_loss, learning_rate, beta1):
"""
Get optimization operations
:param d_loss: Discriminator loss Tensor
:param g_loss: Generator loss Tensor
:param learning_rate: Learning Rate Placeholder
:param beta1: The exponential decay rate for the 1st moment in the optimizer
:return: A tuple of (discriminator training operation, generator training operation)
"""
# TODO: Implement Function
t_vars = tf.trainable_variables()
d_vars = [var for var in t_vars if var.name.startswith('discriminator')]
g_vars = [var for var in t_vars if var.name.startswith('generator')]
with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)):
d_opt = tf.train.AdamOptimizer(learning_rate=learning_rate, beta1=beta1).minimize(d_loss, var_list=d_vars)
g_opt = tf.train.AdamOptimizer(learning_rate=learning_rate, beta1=beta1).minimize(g_loss, var_list=g_vars)
return (d_opt, g_opt)
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
tests.test_model_opt(model_opt, tf)
"""
DON'T MODIFY ANYTHING IN THIS CELL
"""
import numpy as np
def show_generator_output(sess, n_images, input_z, out_channel_dim, image_mode):
"""
Show example output for the generator
:param sess: TensorFlow session
:param n_images: Number of Images to display
:param input_z: Input Z Tensor
:param out_channel_dim: The number of channels in the output image
:param image_mode: The mode to use for images ("RGB" or "L")
"""
cmap = None if image_mode == 'RGB' else 'gray'
z_dim = input_z.get_shape().as_list()[-1]
example_z = np.random.uniform(-1, 1, size=[n_images, z_dim])
samples = sess.run(
generator(input_z, out_channel_dim, False),
feed_dict={input_z: example_z})
images_grid = helper.images_square_grid(samples, image_mode)
pyplot.imshow(images_grid, cmap=cmap)
pyplot.show()
Implement train
to build and train the GANs. Use the following functions you implemented:
model_inputs(image_width, image_height, image_channels, z_dim)
model_loss(input_real, input_z, out_channel_dim)
model_opt(d_loss, g_loss, learning_rate, beta1)
Use the show_generator_output
to show generator
output while you train. Running show_generator_output
for every batch will drastically increase training time and increase the size of the notebook. It's recommended to print the generator
output every 100 batches.
def train(epoch_count, batch_size, z_dim, learning_rate, beta1, get_batches, data_shape, data_image_mode):
"""
Train the GAN
:param epoch_count: Number of epochs
:param batch_size: Batch Size
:param z_dim: Z dimension
:param learning_rate: Learning Rate
:param beta1: The exponential decay rate for the 1st moment in the optimizer
:param get_batches: Function to get batches
:param data_shape: Shape of the data (batch, h, w, c)
:param data_image_mode: The image mode to use for images ("RGB" or "L")
"""
# Build Model
height = data_shape[1]
width = data_shape[2]
channels = data_shape[3]
#Get input placeholders
input_real, input_z, learning_rate_p = model_inputs(width, height, channels, z_dim)
# Get losses
d_loss, g_loss = model_loss(input_real, input_z, channels)
# Get optimizer
d_opt, g_opt = model_opt(d_loss, g_loss, learning_rate_p, beta1)
# Prepare the shape of z input for testing during training (zero filled tensor of the correct shape)
test_images = 16
test_z_shape = tf.zeros((test_images, z_dim))
print_batch_step = 100
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch_i in range(epoch_count):
batch_index = 0
for batch_images in get_batches(batch_size):
# TODO: Train Model
# real images are in range [-0.5, 0.5], while generated are in range [-1.0, 1.0]
batch_images = batch_images * 2
# Initialize a input z for the batch
batch_z = np.random.uniform(-1, 1, size=(batch_size, z_dim))
# Run optimizers
_ = sess.run(d_opt, feed_dict={input_real: batch_images, input_z: batch_z,
learning_rate_p: learning_rate})
_ = sess.run(g_opt, feed_dict={input_z: batch_z, input_real: batch_images,
learning_rate_p: learning_rate})
# Print losses
if batch_index % print_batch_step == 0:
train_loss_d = d_loss.eval({input_z: batch_z, input_real: batch_images})
train_loss_g = g_loss.eval({input_z: batch_z})
print("\nEpoch {}/{}...".format(epoch_i + 1, epoch_count),
"Discriminator Loss: {:.4f}...".format(train_loss_d),
"Generator Loss: {:.4f}".format(train_loss_g))
if batch_index % print_batch_step == 0:
# Here test_z_shape is used only to retrieve the shape, the function initialize
# another random z to test the generator
show_generator_output(sess, test_images, test_z_shape, channels, data_image_mode)
batch_index += 1
Test your GANs architecture on MNIST. After 2 epochs, the GANs should be able to generate images that look like handwritten digits. Make sure the loss of the generator is lower than the loss of the discriminator or close to 0.
batch_size = 64
z_dim = 100
learning_rate = 0.0002
beta1 = 0.5
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
epochs = 2
mnist_dataset = helper.Dataset('mnist', glob(os.path.join(data_dir, 'mnist/*.jpg')))
with tf.Graph().as_default():
train(epochs, batch_size, z_dim, learning_rate, beta1, mnist_dataset.get_batches,
mnist_dataset.shape, mnist_dataset.image_mode)
Run your GANs on CelebA. It will take around 20 minutes on the average GPU to run one epoch. You can run the whole epoch or stop when it starts to generate realistic faces.
batch_size = 32
z_dim = 100
learning_rate = 0.0002
beta1 = 0.5
"""
DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE
"""
epochs = 1
celeba_dataset = helper.Dataset('celeba', glob(os.path.join(data_dir, 'img_align_celeba/*.jpg')))
with tf.Graph().as_default():
train(epochs, batch_size, z_dim, learning_rate, beta1, celeba_dataset.get_batches,
celeba_dataset.shape, celeba_dataset.image_mode)
When submitting this project, make sure to run all the cells before saving the notebook. Save the notebook file as "dlnd_face_generation.ipynb" and save it as a HTML file under "File" -> "Download as". Include the "helper.py" and "problem_unittests.py" files in your submission.


$ownername$ $safeprojectname$ Module
Thank You!
Thank you for creating your DNN Extensions with my templates.
I maintain these templates because I get great enjoyment out of helping our with the DNN community (it will always be DotNetNuke to me).
If you would like to thank me for my efforts you can shoot me some cash on paypal<EMAIL_ADDRESS>or venmo (@christoc)!
You can find me online at https://twitter.com/christoc
-Chris Hammond (@christoc)
* Important *
When creating a project using this template, you should have named your project
simply "ModuleName", not "$ownername$ModuleName". If you didn't do this, the
easiest thing to do is close the solution, delete the folder from your DesktopModules
folder, then create a new project.
Due to limitations in the templating capabilities of Visual Studio, you will need
to make some minor changes, and double check a few things, before you package this module.
-
Open up IIS Manager (start>run>INETMGR), go to your $devenvironmenturl$ website and make sure that DesktopModules is NOT a Virtual Directory/Application, if so, remove that. You can also look at sub directories under that folder that are also virtual directories, right click and remove them as well.
-
You will need to change the TARGET Framework to 4.7.2 if you're compiling against DNN 9.5+. Right click on the project, choose properties, change the target. If you change the TARGET framework for the project, remove the WEB.CONFIG file that Visual Studio will add to your project.
-
BEFORE checking the
module into your source control environment check the following.
-
Edit the Project properties by double-clicking on the Properties folder in Solution
Explorer
-
On the Web tab:
- Under 'Start Action', make sure to set your Start URL to the correct site (ex: http://$devenvironmenturl$/).
-
Under 'Servers', make sure to "Use IIS Web Server" with a project url of http://$devenvironmenturl$/desktopmodules/$safeprojectname$
and an Override application root URL of http://$devenvironmenturl$/
DotNetNuke Module Development Tutorials
Steps after your project is setup.
-
To Build and Package your Module you need to change to Release mode and then
simply choose Build from the Build menu. The MSBuild scripts will do the packaging
process for you. The packaging process creates an INSTALL and a SOURCE package in
the INSTALL folder within your desktopmodule/modulename folder.
-
Installing your module in your development DotNetNuke instance is easy.Take the
INSTALL ZIP file that is created in the INSTALL folder, upload that using the Host/Extensions
page, Installation Wizard. Using the INSTALL ZIP file will overwrite the files already in this folder,
but that shouldn't cause any problems as long as you install a recent build.
-
After you've completed a release for your module (typically deploying to an environment
outside of your development environment) you should change the Version number, you
need to do this in two places, the assemblyinfo file, and the .DNN manifest file.
Next time you build the module in release mode you will get a new package (using
the version number) created.
- For more information visit our DotNetNuke Consulting page.
After you are finished reading this Documentation you can delete the Documentation directory
from your project.


- LIVY_URI_KEY - Static variable in class org.apache.livy.LivyClientBuilder
-
- LivyClient - Interface in org.apache.livy
-
A client for submitting Spark-based jobs to a Livy backend.
- LivyClientBuilder - Class in org.apache.livy
-
A builder for Livy clients.
- LivyClientBuilder() - Constructor for class org.apache.livy.LivyClientBuilder
-
Creates a new builder that will automatically load the default Livy and Spark configuration
from the classpath.
- LivyClientBuilder(boolean) - Constructor for class org.apache.livy.LivyClientBuilder
-
Creates a new builder that will optionally load the default Livy and Spark configuration
from the classpath.
- LivyClientFactory - Interface in org.apache.livy
-
A factory for Livy clients.


Hello there!
This is my first commit on git cli.
Its really exciting to be here
Thank you! :)
Thanks for visiting!
GF: No.
BF: Baby are you jealous?
GF: No.
BF: Baby are you jealous?
GF: I already told you, No!
BF: Baby can I get a kiss?


The San Francisco Federal Building Plaza was a redesign project that started out as a submission to a competition hosted by the government building. The prompt for the competition was open ended: “Envision a design intervention that activates the New San Francisco Federal Building plaza.” In a team of four, I worked as the architect and visual planner. Synthesizing city planning theories, empirical observations and interviews of plaza visitors, and the research my group came up with, I re-designed the look and feel of the plaza. See our final slide deck here.


A SHORT HISTORY OF TIME A film review by Lons Ponschock Copyright 1996 Lons Ponschock
" My dreams at that time [1963] were rather disturbed. Before my condition had been diagnosed, I had been very bored with life. There had not seemed to be anything worth doing. But shortly after I came out of hospital, I dreamt that I was going to be executed. I suddenly realized that there were a lot of worthwhile things I could do if I were reprieved."
- Stephen Hawking - An egg is an idea in the mind of a chicken to get more eggs.
- Author Unknown -
A BRIEF HISTORY OF TIME directed by Erroll Morris
This is the documentary on the life and work of theoretical cosmologist Stephen Hawking. I am in the position here similar to the anecdote about some sports figure who wrote a book and had to have someone else read it to him.
In due time, the other Netters here will be able to explain to us what the actual import of Hawking's work is. As with every great mind and new scientific discovery we are reminded of the phrase from the Firesign Theater: "Everything you know is wrong." If I interpret what is said correctly, Hawking is saying that the steady state theory of the cosmology refutes Einsteinian physics which is to say that it is Hawking's contention that God truly _does_ play at dice with the Universe and occasionally throws them in the corner where they cannot be seen: that there is true randomness in the Universe.
In propounding the Steady State Theory (that the Universe is continually expanding,) it is his contention that there is no God or that there are limits to the nature of God's work. (Neither Hawking nor Morris are familiar with the work of Julian Jaynes, but this is a digression.) God can only exist as The Creator if there is a beginning to Time: The Big Bang theory which is the idea that the Universe began as a violent explosion fusing bits together to form matter: the star stuff of which Carl Sagan speaks.
Morris' documentary combines theory with biographical details from Hawking's life which were most enjoyable. The naturalness with which Hawking was able, through his genius, to convert from an analytical method to a conceptual method due to his growing infirmities from a progressive illness is the center on which the story turns. This is to say that _without_ the infirmities, those conceptualizations of the Universe; Black Hole constructions and a model of the Universe based on a _reverse_ Black Hole may never have taken place.
The film discusses such topics as Imaginary Time, Singularities and smooth troughs in Black Holes.
-------------------------------------------------
There is a story in the mysteries of the Hassidim concerning the student who told his rabbi that he was not satisfied with the way God made the Universe. The teacher asks the student, "Do you believe that you could have done better?" The student replies, "Yes, Rebbe, I believe that I could." The Master picks up the student by the front of his garment, looks the young man in the eye and with great force says, "Then BEGIN!"
- source obscure -
-------------------------------------------------- For the discerning viewer, I suggest that after watching this documentary a couple of times... which you will *need* to do... that you go out and buy some Louis Prima records.
lon
-- "I did not know the cure for the disease of images, but I believed in the healing power of words and stories." - Until the End of the World -<EMAIL_ADDRESS>The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


This application is dedicated to my final project, in which is possible to operate over any TestLink instance reachable through Internet. You can check the different projects, plans, build and test cases, the latest executions and also execute the test filtering by browser or technology and upload attachments


{% extends "base.html" %} {% block title %}404: Page Not Found Error{% endblock %} {% block bodycontents %}
The page you requested could not be found. We're terribly sorry for this inconvenience.
It's possible that the page you're trying to view has been deleted, or the URL has changed. If you think something might be amiss, please feel free to report this error by emailing<EMAIL_ADDRESS>Please be sure to include the URL and error in your email.


Tony Blair says Conservatives and Labour are 'peddling fantasies' – video
by ReutersThe former PM has accused Labour and the Tories of 'peddling two sets of fantasies'. Blair, speaking at a media event in London, said 'both, as majority governments, pose a risk it would be unwise for the country to take'.


^n ^p # up/down ^f ^b # left/right
^v Mv # up/down page
^a ^e # begin/end of line Ma Me # begin/end of sentence
^x ^f # find file ^x ^s # save file
Mx
Mx package-install RET evil RET


I am Lotanna Agu
Software Developer
I’m a recent graduate of the University of Lagos under the Department of Electrical and Electronics Engineering where I’m a member of a number of academic societies, and to an advantage I’ve been an ambassador to a few of the programs which have held in the university, the HSE (Health Science and Environment) Program for example. My experiences have involved creating awareness of such programs to a great percentage of my colleagues at school, which have helped in boosting my verbal communication and interpersonal skills. I’ve also participated in extracurricular practical-oriented engineering programs, like the Business Electronics Leadership Technology (BELT) Series Program, which have given me a measure of experience and exposure to practical engineering methods.


Olympique Lyonnais' shares rise after Benfica Champions League win
REUTERS: Shares in French soccer club Olympique Lyonnais rose on Wednesday after Lyon revived their hopes of reaching the Champions League last 16 with a victory over Benfica.
Lyon's shares were up around 1per cent in early trading.
Lyon beat Benfica 3-1 on Tuesday evening.
(Reporting by Sudip Kar-Gupta; editing by David Evans)


{% extends "base.html" %} {% block title %}Remove a character{% endblock %} {% block content %} You pulled a new character, but your roster was already full! You must either remove a character to make space or discard {{ pulled_char[1]['name'] }}.


Well, you are not a “Security Specialist” if you …
Starting this thread is easy …
I spotted many of people nowadays claimed that they are an “InfoSec Specialist” on their resume, Linkedin profile, etc. This will eventually makes life a bit more difficult for the HR personnel or inexperience hiring managers that are genuinely looking for a real-deal InfoSec Specialist to fill certain specifi job roles.
用講的很簡單 …
我發現現在很多人在他們的履歷、Linkedin 專頁等等，宣稱自己是”資安專家”。這只是在為難想找真正的專家來填補工作空缺的那些 HR 或人事經理人們而已。
So, here are things that separate them from the real specialists and experts out there. See if you may be considered into one of them (inadvertently or deliberately):
所以，這裡是一些可以用來區分他們是否為真正的專家，或是熟捻相關事務的從業人員，或許你會想考慮其中一個職業（不小心或蓄意的）。
- You are not a Pentesting/Ethical Hacking Specialist if your day to day job just utilzing some tools to look for vulnerability such as Qualys, Nessus, OpenVAS, Nikto, Acunetix, and alike. We called these tools: Automated Vulnerability Scanners and using one of them does not makes you a Hacker or Pentester Specialist. To makes you a real Pentester, you need to be able to break into the systems manually and be able to gain root/administrator privileges on that system you are breaking (in which pratically allowed you to do anything you wanted with that compromised system). Alternatively you should be able to demonstrate the capability to ex-filtrate any sensitive information out from its protected repositories even if you only given a standard user privilege access to that system.
你不算是一個專業滲透測試/道德駭客，如果你每天的工作內容都是，使用類似 Qualys、Nessus、OpenVAS、Nikto、Acunetix 之類的工具尋找弱點。我們稱這些工具為：自動化弱點掃描器，使用其中一個工具並不會讓你變成駭客或是滲透測試專家。想當一個真正的滲透測試者，你需要要能手動侵入一個系統並且獲得該系統的管理員權限（表示在那被攻陷的系統上你可以為所欲為）。或是你應該能展示出從一個被保護的存放處洩漏出敏感資訊的能力，甚至你只有被給予一個該系統上一般的使用者權限。
- You are not a Malware Reverse Engineering Specialist if what you are doing simply googling the executable filenames or its MD5 hashes and look it up at VirusTotal or take that executables and run it into the Automated Malware Sandbox Analyzer such as Cuckoo, VT, Malwr, GFI, etc. to be called a real Malware Revese Engineering Specialist, you should be able to perform surgical of the malicious binary files using Hex Editor, Debugger and Disassembler and MOST IMPORTANTLY to be able to decode, de-obfuscate and probaly decrypt the codes and login behind the binary file and locate in which part of the code’s sub-routine that is/are doing evil as to validate if the binary is truly malicious.
你不算是一個專業惡意程式逆向工程師，如果你只是簡單的 google 執行檔的名稱或是他的 MD5 雜湊並在 VirusTotal 搜尋它，或是丟到 Cuckoo、VT、Malwr、GFI 等等的自動化惡意程式沙盒分析工具。要想成為一個真正的惡意程式逆向工程專家，你應該要能使用十六進位編輯器、除錯器、反組譯器剖析惡意程式二進位檔案，還有最重要的是可以解碼、解混淆並正確的解密程式碼，並且定位到執行惡意行為的程式區段，如果它是真的惡意程式的話。
- You are not a Cyber Threat Intelligent Specialist if what you do is just to read and forward InfoSec news to your bosses/IT Team without having a proper method on how to dissect, filter and process that information into a valuable intelligence that are useful for your organization in term of how to provide early detection, prevent and deter the cyber attack or casualties from arising or happening to your ogranization.
你不算是一個網路威脅情資專家，如果你只是純粹讀一讀資安新聞，或是轉發資安新聞給你老闆/IT 團隊。在危害發生前，你需要提供一個適切的方法來處理該資訊成為對你的組織有價值的情資，以達到早先偵測、避免與阻斷網路攻擊的目的。
- You are not a Network Attack/DDoS Mitigator Specialist if what you do is merely to have your inbound internet link traffics routed behind a DDoS Scrubbing Provider (like Prolexic/Akamai, Verisign, Incapsula, etc) and you were involved to any DDoS attack events just because you were being called /paged-out by oyur DDoS cloud provider to joined their bridgeline and listening to what they are doing over the phone. To be called a specialist in this field, you need to know how exactly the Network/DDoS attacks coming to your network: the attack vectors, methods, protocols being abused, types of attacks, what is the mitigation control you have in-premise and on-the-cloud, when and what mitigations to activate, etc. You need to be proficient in reading and understand the underlying of packet captures and you also need to know how to build and enhance your defense posture to adapt with the ever increasing attack methods being launch and seen to-date.
你不算是一個網路攻擊/分散式阻絕緩解專家，如果你只是修改路由將網際網路的輸入流量轉接到 DDoS 流量清洗商(例:Prolexic/Akamai, Verisign, Incapsula…)或只從鍵盤參與或是與你的 DDoS 流量清洗商用電話溝通加入他們的實況轉播。 這方面的專業人士必須真正了解網路與 DDoS 攻擊 是如何進入你的網路，例如: 清楚攻擊向量、方式、使用的通訊協定、攻擊類型，地端與雲端可用的緩解方案，緩解方案的啟動時機。 舉例來說，你必須熟悉如何獲取封包並且能夠閱讀內容，同時你必須能夠建立並加強自己的防禦機制去解決與日俱增的攻擊方式和現今正在發生的攻擊。
- You are not Security Event Management Specialist if what you do is just to received event alerts from your IDSes or Log alerting/correclation tools (such as Splunk, ArcSight ESM, etc) and escalate this alerts to your security vendor whom job function is simply doing Level-1 of event forwarding blindly to the proper team. In order to makes you a real Security Event Management Specialist, you need to be able to do some basic analysis of the events and to determine which ones are real events and which ones are false positive. You also needs to have capability to filter out and reduced any false positives by tuning the SIEM system you have access to.
你不算是一個資安事件管理專家，如果你只是從你的入侵偵測系統(IDS)收警告或日誌告警/關聯工具 (諸如：Splunk、ArcSight ESM 等)並將告警呈報給你的資安供應商，這種初級且簡單到可以矇眼轉送的工作。為了成為一個正港的資安事件管理職人，你需要能實作初步的分析判斷哪些是誤報，哪些是真正的告警。你也需要會調校你的 SIEM 系統以達到過濾及減少誤報。
- You are not Security Intrusion Analyst/Specialist if you are simply doing the above job roles of Security Event Management Specialist. To makes you a real deal Seucrity Intrusion Analyst/Specialist, you need to be able to read inside packet capture and tell us on the spot of what is it raelly happening on any particular events. You also will need to be able to perform a holistic analysis even in the case where full packet capture is not available and you need to rely on other means likes system/proxy/firewall/network logs etc.
你不能算是正港的資安入侵分析師/專家，如果你只是做一些上面提到資安事件管理專家做的事情。要想當個真真正正的資安入侵分析師/專家，你需要有透過封包分析並告訴我們目前到底發生了什麼事情。同時也要在封包收錄不足的情況下，佐以系統/代理伺服器/防火牆/網路日誌等等，做到一個完整的解析說明。
- You are not a Computer Forensic Specialist if what you konw is just to check the system logs from any error message and to run anti-virus software to find out whether or not the system is infected by viruses. To be called a Computer Forensics Specialist, you need to be able to perform proper data acquisition of the evidence from HDD or any other storage, to preserve it well and ensure it is adminssible to the court during trials, to be able to dig deeper down to the file system level of different OSes to find out artifacts of events being investigate, detecting and recovering deleted files from slack spaces and even to recover evidence from volume shadow copies, registry entries, prefetch data, etc. You also need to be proficient with handling volatile memory, know how to acquire them as well as to find malicious code could possibly hiding into other legitimate process(es) via process hollowing, etc.
你不能算是一個計算機鑑識專家，如果你只是看看系統日誌的錯誤訊息，並運行防毒軟體來確認是否有感染跡象。要想當個 real 的計算機鑑識專家，你要會從 HDD 或其他存儲裝置獲取恰當的跡證資料，並將之保存得當並確定這是可以在審訊中做為依據的。要能深入不同作業系統上的檔案系統層級，去找尋正在調查的人為事件、偵測並從殘餘空間復原被刪除的檔案，甚至從 volume shadow 拷貝、註冊表、prefetch file 等等來復原證據，你也需要專業到可以處理揮發性記憶體，知道如何從中取得可能存在合法形成中的惡意程式碼。
Hope this article could provide some insight to any HR practitioner specializing in InfoSec recruitment or to any hiring managers that are truly looking for a better (if not one of the best) InfoSec Specilists out there to perform a real challenging jobs that required their true expertise of their respective fields.
希望這個文章可以讓與資安相關的 HR 從業人員或人事經理多了解一點，在資安相關工作雇用上可以找到好一點的(如果不是要最好的)資安專家，來實踐一些真正需要他們專業及屌炸天的技能來應付的挑戰。


General Information
- Breed Purpose
- Ornamental
- Comb
- Walnut
- Broodiness
- Frequent
- Climate Tolerance
- All Climates
- Egg Productivity
- Low
- Egg Size
- Medium
- Egg Color
- Light Brown
- Breed Temperament
- Easily handled,Calm,Docile
- Breed Colors/Varieties
- Black, White, Buff, Partridge, Blue, Splash, Gray.
- Breed Size
- Bantam
Sizzles are a very new breed and a work in progress. They have been seen around since the 1990's but have a long way to go to be considered as a recognized breed. People need to start showing them in sactioned show for the process to move forward.


It's our responsiblity to keep you updated, This version of app is no longer supported, check out what's new in updated app.


The currency conversion library is an extension to the SAP Cloud SDK. You can use this library to facilitate currency exchange rate conversion.
The library does not provide any out of the box market data. You must provide the currency exchange rates to the library from your own data sources. You can use the SAP Market Rates Management service or any other service of your choice.
You can use the library for the following functions:
The library provides the following modules:
Get currency exchange rates converted from a source currency to a target currency by using the currency conversion library.
Multiple currency pairs can be converted through a single call. You can use the library methods to get more than one currency pair converted simultaneously.
Get direct and indirect currency conversions for your currency pairs. Direct conversions are one-to-one conversions between a source and a target currency. Indirect conversions are performed by using inverse rates or a reference currency.
The currency conversion library requires:
Install the currency conversion library using npm:
npm install @sap-cloud-sdk/currency-conversion-core, @sap-cloud-sdk/currency-conversion-data-adapter, @sap-cloud-sdk/currency-conversion-models, @sap-cloud-sdk/currency-conversion-integration-objects
The following code examples show the various usages of the library:
The GitHub issue tracker is the preferred channel for reporting bugs, feature requests, or querying.
This project is licensed under Apache 2.0 as noted in the license file.


If you're a student, let us know what you're interested in experiencing.
We go through all of the applications and match Students and Shadow Sponsors based on their interests and offerings.
Students get to check out possible career paths. Employers get to learn from students about new technologies and current trends in academia.


there was a lot of brain pain involve in making this! i very dislike CORS, but, I like epic bypasscors.herokuapp.com. Now with enter!


User authentication gets real boring real fast. It is fun the first couple of times you do it. I remember the exhilaration that I felt when I wrote the authentication for my first ever web application. But after doing this for 108 times I don't particularly enjoy writing for register-login-logout scenario anymore. In fact I think it is one of the slowest most boring parts of the web application design.
It is a thankless job. Do it right and no one pats on your back and says "Well done joe! you wrote beautiful authentication code. Boy I just love signing in and out of this website" But if, God forbid, you get it wrong you end up on hacker news where this "piece of news" stays on the front page for some 23 days giving the entire programming community ample time to revel in your woe and making sure that your reputation as a programmer is completely destroyed. Life is sad, a bitter cookie if you ask me. But there it is.
Wouldn't it be nice if we could "outsource" the user account management to some software so we can focus on more important stuff like working on the actual application rather than writing boiler plate code.  "It would be nice indeed!" But what's that? I detect a slight sarcasm in your voice. You don't trust me. You think it is a pipe dream. I don't blame you. Once bitten twice shy and all that.
Say hello to couchdb.
"Hello to couchdb?"
"Yes hello to couchdb"
"But isn't that-----"
"Yes it is"
"But doesn't it-----"
"No it doesn't"
"Wouldn't it be -----"
"No it wouldn't"
Relax.
So here is the kicker. We are going offload entire account management to couchdb. We will create users in couchdb. We will store their roles in couchdb. And will let couchdb authenticate them while we sit back and feel clever. But before we do that we must learn some basics.
Start of basics tutorial
Step 0: install and start couchdb. The default port that it listens to is 5984 and I am going to assume that you are running on 5984.
Step 1: Create a couchdb administrator
The administrators are defined in the couchdb configuration files.  We will just type a beautiful single line command from our majestic terminal and let couchdb take care of the REST.
curl -X PUT http://localhost:5984/_config/admins/ABBA -d '"dancing-queen"'
You should see the
""
in the response. I guess couchdb was too stunned by your magnificence to make a reply, whatever. Any way lets check our config file to see if we have an admin named ABBA.
curl -X GET http://localhost:5984/_config
{"error":"unauthorized","reason":"You are not a server admin."}
All right we will login as the administrator and see if that helps.
curl -X GET http://ABBA:dancing-queen@localhost:5984/_config
{
.....irrelevant objects we don't care about
admins: {
ABBA: "-pbkdf2-2c3adb095f2c91498e816e6873ac2297f2bba378,11661435b847e1d5ad526b8ee6d36a67,10"
},
..... irrelevant objects we don't care about
}
`
Ahh that's better. Notice how our "dancing-queen" password was automatically saved in a secure hash rather than in plain text. All of this without us having to move our fingers. I mean without us having to move our fingers much.
Step 2 : Add couchdb users
Now that we have an administrator
we will add a few users. All the users in couchdb are stored by default in _users
database. To store
a user you just have to issue a PUT request to the _user
database.
Here's how you do it.
curl -X PUT http://ABBA:dancing-queen@localhost:5984/_users/org.couchdb.user:fernando \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "fernando", "password": "apple", "roles": [], "type": "user"}'
{"ok":true,"id":"org.couchdb.user:fernando","rev":"1-e0ebfb84005b920488fc7a8cc5470cc0"}
And that adds our friend fernando as a user.
At a minimum user document must contain name, password, roles and type field. The type will
always be user
. The _id
field must be prefixed with org.couchdb.user
(we don't have to include _id
in the json body we can simply append it after the _users
in the url).
roles
is an array that can contain anything and name and password are self
explanatory. Besides these fields you are free to add any arbitrary number of fields to the user document.
Let's fetch this document and see how couchdb saves it internally.
curl -X GET http://ABBA:dancing-queen@localhost:5984/_users/org.couchdb.user:fernando/
{
_id: "org.couchdb.user:fernando",
_rev: "1-f8d04469cd45d99c9c7cdf0beda1cf99",
password_scheme: "pbkdf2",
iterations: 10,
name: "fernando",
roles: [ ],
type: "user",
derived_key: "1864805738e4515cb677b4b55e99beaa844c0998",
salt: "bba686808c217c316604c84f15f666b5"
}
Ah once more we see that passwords are not stored in plain text. This makes us :-) no?
End of basics tutorial
We are almost done! we still have to see how to manage sessions though. But first let me run quickly and get a refill of the custard while everyone is still sleeping.
You know how easy it is to eat custard right? Dip in a spoon inside the bowl, bring the spoon filled with custard to the brim right up to your lips. Part your lips to about the width of the spoon(it might be a difficult estimation if this is your first time but soon you will be doing it without giving it a second thought).  At this point your sweet receptors should sense the angelic taste of custard which will melt in your mouth as you slowly try to chew it but discover that you can't and regretfully gulp it in hoping the next serve would last longer. Life is a tease. But there it is.
How to manage sessions? Right ho! all you got to do is make a post request to
_session
endpoint
curl -X POST http://localhost:5984/_session -d 'name=fernando&password=apple'
HTTP/1.1 200 OK
**Set-Cookie: AuthSession=amFuOjUzQzE4MjY4Opq9wMW4hZALkEqoZagHgu_v3-rT; Version=1; Path=/; HttpOnly**
Server: CouchDB/1.6.0 (Erlang OTP/17)
Date: Sat, 12 Jul 2014 18:46:00 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 36
Cache-Control: must-revalidate
{"ok":true,"name":"fernando","roles":[]}
Ah yes we have the cookie
Let us use it to get session information
curl -X GET http://localhost:5984/_session -H " Host: localhost:5984
Accept: application/json
Cookie: AuthSession=amFuOjUzQzE4MjY4Opq9wMW4hZALkEqoZagHgu_v3-rT"
{
ok: true,
userCtx: { name: "fernando", roles: [ ] },
info: {
authentication_db: "_users",
authentication_handlers: ["oauth", "cookie", "default"],
authenticated: "cookie"
}
}
Nice. usrCtx
has the name and the roles. Just what we want.
But there are still some adjustments that we need to do.
Some adjustments
I think it is safe to assume that most of us will be using a middleware in front of couchdb. So we must find a way to extract this cookie header from couchdb response and send it to the browser to save it. Thankfully it's not too tough. Let us take a look at the example using nginx and lua (open resty)
location /couch_session{
proxy_pass http://localhost:5984/_session;
}
location /login {
content_by_lua
'
ngx.req.set_header("Host","localhost:5984")
ngx.req.set_header("Content-Type","application/json")
local res = ngx.location.capture(
"/couch_session",{method=ngx.HTTP_POST,body=cjson.encode({name="fernando",password="apple"})});
local cookie = res.header["Set-Cookie"]
ngx.header["Set-Cookie"] = cookie
ngx.say("Logged in sir");
';
}
location /logout{
content_by_lua
'
local cookie = ngx.req.get_headers()["Cookie"]
ngx.req.set_header("Cookie",cookie)
ngx.req.set_header("Host","localhost:5984")
local res = ngx.location.capture("/couch_session",{method=ngx.HTTP_DELETE});
ngx.header["Set-Cookie"] = res.header["Set-Cookie"]
ngx.say(res.body)
';
}
The code above defines two handlers :- login and logout. The approach is similar for both of them
For login
_session
api with the credentials. Make sure to set the Host
to where your couchdb is running.Set-Cookie
header.Set-Cookie
with the response to the client. For logout
_session
api with the cookie we
extracted in the first step. Make sure to set the Host
to where your couchdb is running.Set-Cookie
(which will be null) to the client.And so in a couple of lines we have a fully functional session and user management code on our hands that is secure (remember that all passwords are hashed) and well tested.
Addressing a potential complaint
Now you might be wondering if it is not a bit of an overkill to install a database just to do user management. It depends upon your outlook.
First of all couchdb is very light weight. Unless you look very closely
you would not even notice it running on your machine.
As a test I added a hundred thousand users with
roles:["vassal","mama-mia"]
and the reported memory usage for couchdb was a meager 22MB.
If this does not convince you let me present some other useful features. Couchdb can cache authenticated users in memory to reduce disk lookups. This makes session management very fast.
The configuration parameter is auth_cache_size
. By default it is configured to hold 50 documents.
The actual size of the auth cache would depend upon the maximum number of logged in users that
your application experiences at peak time. For this you will have to run tests. But I am going to be reckless here
and store all of my 100K users in the cache. For that we will need to override the default configuration.
curl -X PUT http://ABBA:dancing-queen@localhost:5984/_config/couch_httpd_auth/auth_cache_size -d '100000'
Now I will have to make authentication requests to the _session
(just like we did earlier) handler to actually
put them in a cache.
Please read this totally relevant other article while I do that.
28 minutes later
Ahh it's all done now. All of 100000 users are in the cache. Before we dive deeper into the cache it is time for some tests. Now I suppose you don't have a 100k users handy. No problem. One will do.
sudo couchdb -i
_stats
handlercurl http://localhost:5984/_stats
{
couchdb: {
auth_cache_misses: {
description: "number of authentication cache misses",
current: 100000,
sum: 100000,
mean: 8.148,
stddev: 20.337,
min: 0,
max: 62
},
.......irrelevant objects that we don't care about.
auth_cache_hits: {
description: "number of authentication cache hits",
current: 8,
sum: 8,
mean: 0.001,
stddev: 0.045,
min: 0,
max: 3
}
.... irrelevant objects that we don't care about.
}
}
Couch db will respond with some statistics that it diligently collects.
But at this moment we only care about two of them. auth_cache_misses
and auth_cache_hits
.
My auth cache misses were sum: 100000
that is none of the users were in cache by default.
This number should be 0 for you as we have not tried getting session for a user at the moment. Disregard
the auth_cache_hits
for now.
Now we will test if the user is actually in the cache.
First the old way
_session
handler passing
in the cookie returned in the first step just like we did before_stats
handler. You should see non zero numbers in both cache hits and misses.Explanation
At first fernando is not in the cache list since we restarted couchdb.
Then after we make a request to _session
couchdb creates a session for fernando
and puts him in the cache and increments the cache miss counter.
Finally when we request the credentials of fernando using the
supplied cookie couchdb looks up in the cache and returns the results and increments the
cache hit counter.
Using couchdb shell
I don't know about you but I am getting mighty tired of keeping track of cookies from my terminal. Therefore we will just use the couchdb shell to trick it into authenticating our user without the cookie. Simply do
couch_auth_cache:get_user_creds("frenando").
and press enter. You should see something like this
[{<<"_id">>,<<"org.couchdb.user:fernando">>},
{<<"_rev">>,<<"1-f8d04469cd45d99c9c7cdf0beda1cf99">>},
{<<"password_scheme">>,<<"pbkdf2">>},
{<<"iterations">>,10},
{<<"name">>,<<"fernando">>},
{<<"roles">>,[]},
{<<"type">>,<<"user">>},
{<<"derived_key">>,
<<"1864805738e4515cb677b4b55e99beaa844c0998">>},
{<<"salt">>,<<"bba686808c217c316604c84f15f666b5">>}]
Now run that _stats
handler once more and you should see
auth_cache_hits: {
description: "number of authentication cache hits",
current: 8,
sum: 8,
mean: 0.001,
stddev: 0.045,
min: 0,
max: 3
}
a number greater than 0 for current
and sum
for auth_cache_hits
while there will be no change at all for the auth_cache_miss
.
I loved authenticating from shell so much that I did it
8 times which is why you see the number 8 in my results. Anyway it's time to dig a little bit
deeper.
|
|
|
|
(80 feet below)
|
|
(140 feet below)
How does couchdb cache the users?
Internally couchdb uses ETS (erlang term storage) to hold the users in a cache. ETS is like a mini redis built into erlang. Let's check it out.
ets:i().
. This will give you a list of all the ETS tables
that couchdb uses. The list will be divided into 5 coloumns: id,name,type,size,mem and owner. couch_auth_cache
and especially the table
auth_by_user_ets
.ets:lookup(auth_by_user_ets,<<"fernando">>)
. You should seeets:lookup(auth_by_user_ets,<<"fernando">>).
[{<<"fernando">>,
{[{<<"_id">>,<<"org.couchdb.user:fernando">>},
{<<"_rev">>,<<"1-f8d04469cd45d99c9c7cdf0beda1cf99">>},
{<<"password_scheme">>,<<"pbkdf2">>},
{<<"iterations">>,10},
{<<"name">>,<<"fernando">>},
{<<"roles">>,[]},
{<<"type">>,<<"user">>},
{<<"derived_key">>,
<<"1864805738e4515cb677b4b55e99beaa844c0998">>},
{<<"salt">>,<<"bba686808c217c316604c84f15f666b5">>}],
{1405,341019,516682}}}]
in the result. Sweet isn't it? Everything nicely stored in memory for quick retrieval.
There is just one more small test that we need to do. That is we need to check if the ets table is actually holding the correct result. This is simple we ask the table to lookup for a user that is not there.
ets:lookup(auth_by_user_ets,<<"some-unknown-user">>).
[]
We get an empty list just like we expected.
How much space the tables use largely depends upon the amount of data that is stored
within them. For me with a 100k users the auth_by_user_ets
table took 103 MB.
The memory usage of couchdb bumped up to 160 MB. This means that I can do authentication
for 100k users right from the memory in under 200 MB of space. I'll take that deal.
Some other configuration options
You can also configure couchdb to persist your cookies. All you got to do is set a config property
curl -X PUT http://ABBA:dancing-queen@localhost:5984/_config/couch_httpd_auth/allow_persistent_cookies -d '"true"'
"false"
Now lets test this
curl -X POST http://localhost:5984/_session -d 'name=fernando&password=apple' -v
> POST /_session HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:5984
> Accept: */*
> Content-Length: 23
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 23 out of 23 bytes
< HTTP/1.1 200 OK
**< Set-Cookie: AuthSession=amFuOjUzQzJENTVGOtZa0E2aNe_CtdCnDs5SDAkva02U; Version=1; Expires=Sun, 13-Jul-2014 13:32:15 GMT; Max-Age=600; Path=/; HttpOnly**
* Server CouchDB/1.6.0 (Erlang OTP/17) is not blacklisted
< Server: CouchDB/1.6.0 (Erlang OTP/17)
< Date: Sun, 13 Jul 2014 18:52:15 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 36
< Cache-Control: must-revalidate
<
Ah there is somthing new here. An expires header. Now the browser won't delete the cookie as long as it is not expired.
You can also do other neat things like changing the hashing algorithm and
the default _user
database. For that you will have to dig through the
_config reference
If cookie based authentication is not your style then you can configure couchdb to use OAUTH. But that will be a topic for another post.
Closing
This post turned out to be a lot longer than I had initially expected. The idea was to show how couchdb can be used to simplify the user management process for your application. I think that couchdb offers many advantages that are hard to overlook. It is light. It is secure by default, all the passwords are hashed, it offers an easily configurable environment with which you can enable many other features like session caching for which otherwise you will have to use a third party application anyway. Most importantly it takes just three requests to the http api to have a fully functional register-login-logut workflow in your application. It can't get any easier than that. Life is good. Have fun :)
You might also want to read the official couchdb blog and toptal's fantastic article on 10 Most Common Web Security Vulnerabilities, by Gergely Kalman.


The fieldset below is outside the form, but still a part of the form.
Note: The form attribute of the fieldset element is only supported in Opera 12 and earlier versions.


Runtime: {{movie.Runtime}}
Release Date: {{movie.Released}}
IMDB Rating: {{movie.imdbRating}}/10
Country: {{movie.Country}}
{{movie.Title}}
Director: {{movie.Director}}
Actors: {{movie.Actors}}
Genre: {{movie.Genre}}
Writer: {{movie.Writer}}
Awards & Nominations: {{movie.Awards}}
{{movie.Plot}}
Box-Office: {{movie.BoxOffice}}
Production by {{movie.Production}}


“
Think of us like this, you need a skill set, you have a problem and need a solution to your problem, thats where we come in, we show you how you could solve that problem by showing you the courses you could read and watch and practice till you gain that particular skill set.With this knowledge you can go out to the world knowing that you have something of advantage to offer.


Quite some time ago, Daniel Savard wrote an extension to SQLObject to allow something closer to traditional inheritance. SQLObject already supports inheritance, but it's not what people typically expect, because it just clones a table instead of creating something that looks like a subtable. You can read the description of the patch for the details.
I've meant to respond to the patch for a long time, but I couldn't quite put my finger on what troubled me about it. But I've kind of decided: SQLObject isn't a way to persist your objects in a database. It's not an object->relational database mapper. It's a relational->object mapper, a way to model your relational database as objects. You aren't meant to forget there's a database. It isn't magical, it isn't transparent, and I don't want it to be. If you want transparent, use the ZODB, that's what it's meant for. Or use something like APE or Active Storage if you want a RDBMS backend to a object database. An RDBMS is a viable backend for that kind of work, but the result shouldn't be confused with relational modeling.
I think people should use an RDBMS for its advantages, and there are quite a few advantages. Being object oriented isn't one of them. It's probably not surprising that object oriented extensions to RDBMS systems aren't that popular. PostgreSQL has several such extensions, but I've never used them, nor seen them used in any project I've worked on. They seem like a novelty, an idea that seemed useful but didn't pan out. So I'd rather avoid the whole thing in SQLObject; certainly there is a lot of room for improvement where SQLObject could better model relations, but inheritance isn't one of them.
Spot on, as usual, Ian. You've summed up my philosophy on persistence much more elegantly and succintly than I have ever done.
The only thing I can add is this post http://www.halfcooked.com/mt/archives/000685.html where I link to an interesting essay by Tom Kyte, a well known Oracle guru, and commentary on it by myself and VS Babu.
Ian, I'm an amature SQLObject user and I generally agree with what you're saying, but I would add that in the case of subclasses, it would be nice if the supeclass could find subclass instances. Say, for instance, I have this kind of structure:
- class Server(SQLObject):
- ...
- class NameServer(Server):
- ...
- class WebServer(Server):
- ...
- class FileServer(Server):
- ...
Although the relational way to do this may be to structure one server table with server-type as a foreign key, if you're overloading your SQLObjects with methods appropriate for various kinds of servers, that kind of structure can be inconvenient:
- class FileServer(Server):
- def storeFile(...):
- ...
The way SQLObject creates tables for this makes sense, it's a poor relational model and if I want a way to grab all Server objects based on some criteria, it's troublesome. Of course, you can break up your model into smaller subclasses and tables, but in doing so I think you tend to lose what makes SQLObject so great, its simplicity and the resultingly simple models.
Having said all that, I'm not sure what the solution is or whether I think subtables are a good idea. It's been a while since I tinkered with PostgreSQL's Object-Relational notions, but I remember them being quite limited. Is there a way you could add some methods to superclasses for gathering subclass objects?
-Ken
A polymorphic join would be useful, certainly. This way a common server table could indicate the ID and table name that contains other information (i.e., DNS, web, or file information). There's also other ways to handle this relationally. Some of those techniques will require enhancements to SQLObject (though many of them are quite reasonable to implement in an ad hoc manner).


My favorite places
I am happy when I am in these places:
- my garden
- my kitchen
- the woods
- most other gardens
- Bloomington, IN, the place that feels like home
Here are some places I have enjoyed visiting:
- National Parks: Mount Rainier, Yellowstone, Glacier, Cascades, Olympic
- Israel
- Salmon Huckleberry Wilderness in Oregon
- Many different tidal pools on the Pacific Coast of the western US


| Created: | 1/21/2019 3:21:05 AM |
| Modified: | 4/26/2019 6:54:02 PM |
| Project: | |
| Advanced: |
| Attribute |
|
Public other |
|
Details:
|
|
Public unknown |
|
Details:
|
|
Public noPrecipitation |
|
Details:
|
|
Public unidentifiedSlight |
|
Details:
|
|
Public unidentifiedModerate |
|
Details:
|
|
Public unidentifiedHeavy |
|
Details:
|
|
Public snowSlight |
|
Details:
|
|
Public snowModerate |
|
Details:
|
|
Public snowHeavy |
|
Details:
|
|
Public rainSlight |
|
Details:
|
|
Public rainModerate |
|
Details:
|
|
Public rainHeavy |
|
Details:
|
|
Public frozenPrecipitationSlight |
|
Details:
|
|
Public frozenPrecipitationModerate |
|
Details:
|
|
Public frozenPrecipitationHeavy |
|
Details:
|


--- title: MDK --- {% extends "layouts/default.html" %} {% block page %}
Interactive web components inspired from Material Design using vanilla CSS, JavaScript and HTML. Material Design Kit can be used with or without external libraries such as jQuery, Bootstrap and MDL.
A container element for navigation and other content at the top of the screen with (optional) visual effects based on scroll position.
Demos DocumentationA container element for generic content with (optional) visual effects based on scroll position.
Demos DocumentationA component for cycling through elements with Mouse Grab (desktop) and Touch support.
Demos Documentation


Your browser does not support the video element.
Your browser does not support the audio element.
PLAY


|
JTS Topology Suite version 1.13 | ||||||||
| PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
| SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |
java.lang.Object com.vividsolutions.jts.io.WKTReader
public class WKTReader
Converts a geometry in Well-Known Text format to a Geometry
.
WKTReader
supports
extracting Geometry
objects from either Reader
s or
String
s. This allows it to function as a parser to read Geometry
objects from text blocks embedded in other data formats (e.g. XML).
A WKTReader
is parameterized by a GeometryFactory
,
to allow it to create Geometry
objects of the appropriate
implementation. In particular, the GeometryFactory
determines the PrecisionModel
and SRID
that is
used.
The WKTReader
converts all input numbers to the precise
internal representation.
WKTGeometry: one of WKTPoint WKTLineString WKTLinearRing WKTPolygon WKTMultiPoint WKTMultiLineString WKTMultiPolygon WKTGeometryCollection WKTPoint: POINT ( Coordinate ) WKTLineString: LINESTRING CoordinateSequence WKTLinearRing: LINEARRING CoordinateSequence WKTPolygon: POLYGON CoordinateSequenceList WKTMultiPoint: MULTIPOINT CoordinateSingletonList WKTMultiLineString: MULTILINESTRING CoordinateSequenceList WKTMultiPolygon: MULTIPOLYGON ( CoordinateSequenceList { , CoordinateSequenceList } ) WKTGeometryCollection: GEOMETRYCOLLECTION ( WKTGeometry { , WKTGeometry } ) CoordinateSingletonList: ( CoordinateSingleton { , CoordinateSingleton } ) | EMPTY CoordinateSingleton: ( Coordinate ) | EMPTY CoordinateSequenceList: ( CoordinateSequence { , CoordinateSequence } ) | EMPTY CoordinateSequence: ( Coordinate { , Coordinate } ) | EMPTY Coordinate: Number Number Numberopt Number: A Java-style floating-point number (including NaN, with arbitrary case)
WKTWriter
| Constructor Summary | |
|---|---|
WKTReader()
Creates a reader that creates objects using the default GeometryFactory . |
|
WKTReader(GeometryFactory geometryFactory)
Creates a reader that creates objects using the given GeometryFactory . |
| Method Summary | |
|---|---|
Geometry |
read(java.io.Reader reader)
Reads a Well-Known Text representation of a Geometry
from a Reader . |
Geometry |
read(java.lang.String wellKnownText)
Reads a Well-Known Text representation of a Geometry
from a String . |
| Methods inherited from class java.lang.Object |
|---|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
| Constructor Detail |
|---|
public WKTReader()
GeometryFactory
.
public WKTReader(GeometryFactory geometryFactory)
GeometryFactory
.
geometryFactory
- the factory used to create Geometry
s.| Method Detail |
|---|
public Geometry read(java.lang.String wellKnownText) throws ParseException
Geometry
from a String
.
wellKnownText
- one or more Geometry
specified by wellKnownText
ParseException
- if a parsing problem occurspublic Geometry read(java.io.Reader reader) throws ParseException
Geometry
from a Reader
.
reader
- a Reader which will return a Geometry
read from reader
ParseException
- if a parsing problem occurs
|
JTS Topology Suite version 1.13 | ||||||||
| PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
| SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |