diff --git a/djangoshaker/__init__.py b/djangoshaker/__init__.py new file mode 100644 diff --git a/djangoshaker/admin.py b/djangoshaker/admin.py new file mode 100644 --- /dev/null +++ b/djangoshaker/admin.py @@ -0,0 +1,61 @@ +from djangoshaker.models import Measure, Ingredient, Brand, MeasureConversion, Recipe, RecipeIngredient + +from django.contrib import admin + +def publish_recipe(modeladmin, request, queryset): + """ + Action which publishes the select recipes on the admin page for Recipes. + """ + + for recipe in queryset: + recipe.publish() + +publish_recipe.short_description = "Publish selected recipes" + +class RecipeIngredientInline(admin.TabularInline): + """ + Defines the rendering for the administration of recipes. + """ + + model = RecipeIngredient + extra = 3 + fields = ('amount', 'measure', 'ingredient') + +class RecipeAdmin(admin.ModelAdmin): + """ + Defines the rendering for the administration of recipes. Add the + ingredient-recipe mapping to be show inline. + """ + + inlines = (RecipeIngredientInline, ) + actions = [publish_recipe] + list_display = ('name', 'is_published') + ordering = ['name'] + +class MeasureAdmin(admin.ModelAdmin): + """ + Defines the rendering for the administration of measures. + """ + + ordering = ['name'] + +class IngredientAdmin(admin.ModelAdmin): + """ + Defines the rendering for the administration of ingredients. + """ + + ordering = ['name'] + +class BrandAdmin(admin.ModelAdmin): + """ + Defines the rendering for the administration of brands. + """ + + ordering = ['name'] + + +admin.site.register(Measure, MeasureAdmin) +admin.site.register(Ingredient, IngredientAdmin) +admin.site.register(Brand, BrandAdmin) +admin.site.register(MeasureConversion) +admin.site.register(Recipe, RecipeAdmin) diff --git a/djangoshaker/fixtures/sample.json b/djangoshaker/fixtures/sample.json new file mode 100644 --- /dev/null +++ b/djangoshaker/fixtures/sample.json @@ -0,0 +1,1 @@ +[{"pk": 1, "model": "djangoshaker.measure", "fields": {"marking": "cl", "name": "Centilitre", "description": "Centilitre is a standard metric unit which represents a hundredth part of a litre."}}, {"pk": 2, "model": "djangoshaker.measure", "fields": {"marking": "l", "name": "Litre", "description": "Standard metric unit for liquids. It equals to amount of liquid occupying volume equal to 1 cubic decimetre."}}, {"pk": 3, "model": "djangoshaker.measure", "fields": {"marking": "dl", "name": "Decilitre", "description": "Decilitre is a standard metric unit for liquids equal to one tenth of a litre."}}, {"pk": 4, "model": "djangoshaker.measure", "fields": {"marking": "cup", "name": "Cup", "description": "A cup is a customary unit of measurement used for liquids and granulated ingredients. It is commonly a measure equal to the contents of about 200-250ml (one glass). "}}, {"pk": 5, "model": "djangoshaker.measure", "fields": {"marking": "pinch", "name": "Pinch", "description": "Pinch is a customary unit of measurement that's usually used for measuring granulated ingredients (like salt, sugar etc). It is the amount which you can commonly grab between your fingers."}}, {"pk": 6, "model": "djangoshaker.measure", "fields": {"marking": "drop", "name": "Drop", "description": "A drop is a customary unit of measurement used for liquids and granulated ingredients. It is commonly a very small amount that can be obtained by careful dripping of a liquid."}}, {"pk": 7, "model": "djangoshaker.measure", "fields": {"marking": "tbs", "name": "Tablespoon", "description": "Tablespoon is a customary unit of measurement used for liquids and granulated ingredients. It is a measure of ingredient that can fit into a single standard domestic tablespoon (about 0.5-1cl)."}}, {"pk": 8, "model": "djangoshaker.measure", "fields": {"marking": "slice", "name": "Slice", "description": "A slice is a customary unit of measurement, usually used for solid food. This can include both raw ingredients like fruit and vegetables, as well as various cakes etc.\r\n\r\nIt is commonly made using a knife on the desired food to chop off a piece of it."}}, {"pk": 9, "model": "djangoshaker.measure", "fields": {"marking": "dash", "name": "Dash", "description": ""}}, {"pk": 10, "model": "djangoshaker.measure", "fields": {"marking": "splash", "name": "Splash", "description": ""}}, {"pk": 1, "model": "djangoshaker.ingredient", "fields": {"name": "Blue curacao", "description": "Blue curacao is obtained by the careful distillation of dried bitter-orange peel. It has a mild sweet taste to it. It's very often used for the purpose of adding colour to the drinks. Blue curacao itself is coloured by artificial means."}}, {"pk": 2, "model": "djangoshaker.ingredient", "fields": {"name": "White rum", "description": "White rum is a dry, light-bodied rum, light or completely transparent in colour. It's also lightly sweet in flavour. Produced from sugar cane juice and molasses, normally it has a short ageing period.\r\n\r\nWhite rum is aged usually in common oak casks or stainless steel tanks. Commonly it contains around 40% alcohol.Some types of white rum are also flavoured with different tastes."}}, {"pk": 3, "model": "djangoshaker.ingredient", "fields": {"name": "Coconut rum", "description": "Coconut rum is a type of white rum flavoured with coconut. Coconut flavouring may come both from natural extracts and artificial aromas."}}, {"pk": 4, "model": "djangoshaker.ingredient", "fields": {"name": "Orange liqueur", "description": "Orange liqueur is commonly made using alcohol and dried peel of oranges. It is commonly quite sweet with an after-taste of bitterness. Alcohol percentage can go up to 40%, making it quite easy to get drunk from it."}}, {"pk": 5, "model": "djangoshaker.ingredient", "fields": {"name": "Tequila", "description": "Tequila is a spirit drink made out of blue agave plant. The plant and drink originate from today's Mexico. It has a large percentage of alcohol, with just a slightly distinctive taste which makes it quite popular both for drinking as it is, as well as for mixing it in cocktails."}}, {"pk": 6, "model": "djangoshaker.ingredient", "fields": {"name": "Gin", "description": "Gin is a spirit distilled out of grain wash and then re-distilled with various botanicals in order to extract the aromatic compounds. It is also very often distilled with juniper berries in order to pass their flavour into it. Commonly it has between 37.5% and 40% alcoholic content."}}, {"pk": 7, "model": "djangoshaker.ingredient", "fields": {"name": "Vodka", "description": "Vodka is one of the stronger spirits that can be found in regular stores. It usually lacks any kind of taste, but commonly sports a strong alcoholic smell. It's commonly used on its own, and is particularly popular in Russian and Scandinavian countries.\r\n\r\nVodka is mostly used in cocktails to increase the alcohol levels, and is very easy to mix with other ingredients due to its lack of distinct taste and smell."}}, {"pk": 8, "model": "djangoshaker.ingredient", "fields": {"name": "Irish cream", "description": "Irish cream is a cream liqueur based on Irish whiskey, cream, and other ingredients such as coffee, which can be served on its own or used in mixed drinks or as part of a shot or a whole shot.\r\n\r\nIt is usually very popular in cocktails since it adds a nice creamy taste."}}, {"pk": 9, "model": "djangoshaker.ingredient", "fields": {"name": "Amaretto", "description": "Amaretto is an Italian sweet almond-flavoured liqueur. It is made from a base of apricot or almond pits, sometimes both. It has a sweet dominant taste in drinks. The almond aroma gives a special touch to many drinks which use it as part of a recipe."}}, {"pk": 10, "model": "djangoshaker.ingredient", "fields": {"name": "Coffee liqueur", "description": "Liqueur is a common ingredient of many cocktails. On its own it's usually very sweet and has a strong coffee taste (hence the name, of course). It is rarely used on its own, serving mostly to flavour the cocktails and cookies. It usually has a very strong, dominating taste in cocktails."}}, {"pk": 11, "model": "djangoshaker.ingredient", "fields": {"name": "Milk", "description": "Milk has many purposes. In addition to being drank as it is, it's often used in various recipes, both food and drink.\r\n\r\nA number of domesticated animals are kept at farms solely for the purpose of obtaining milk from them (most commonly cows)."}}, {"pk": 12, "model": "djangoshaker.ingredient", "fields": {"name": "Ice", "description": "Ice is commonly used for cooling down drinks. There are two types of ice used for cocktails - regular ice which can be made by putting moulds filled with water into regular freezer, or the so-called \"cheater ice\". Cheater ice is made in special ice maker machines."}}, {"pk": 13, "model": "djangoshaker.ingredient", "fields": {"name": "Orange juice", "description": "Orange is a citrus fruit rich in vitamins (especially vitamin C). Oranges are commonly sweet and sour, the exact ratio depending on their ripeness and particular type.\r\n\r\nOrange juice is commonly obtained by squeezing out the fruit juice, although most orange juices found in shops contain a lot of artificial flavouring, colours etc."}}, {"pk": 14, "model": "djangoshaker.ingredient", "fields": {"name": "Grenadine", "description": "Grenadine is a red syrup, usually red. It is mostly made from different red fruits like cherry, raspberry, redcurrant, or even blackberry."}}, {"pk": 15, "model": "djangoshaker.ingredient", "fields": {"name": "Lemonade", "description": "Lemonade is made out of lemon juice, water, and sugar. Exact ratios are usually dependant to personal taste."}}, {"pk": 16, "model": "djangoshaker.ingredient", "fields": {"name": "Grapefruit juice", "description": "Grapefruit juice is made out of grapefruits, a subtropical citrus fruit rich in vitamin C. It can be made either out of red or yellow fruits. It is both sweet and bitter at the same time."}}, {"pk": 17, "model": "djangoshaker.ingredient", "fields": {"name": "Pineapple juice", "description": "Pineapple juice is made out of tropical plant by squeezing the juice out of the fruit. It's usually very sweet and refreshing, with a slight hint of sourness."}}, {"pk": 18, "model": "djangoshaker.ingredient", "fields": {"name": "Apple juice", "description": "Apple juice can be commonly found in almost any grocery shop. It's produced through maceration and pressing of apples."}}, {"pk": 19, "model": "djangoshaker.ingredient", "fields": {"name": "Peach schnapps", "description": "Peach schnapps is a (usually) sweet type of schnapps with the peach taste. It usually has a very strong and pleasant aroma."}}, {"pk": 20, "model": "djangoshaker.ingredient", "fields": {"name": "Passion fruit liqueur", "description": "Passion fruit liqueur offers a rich fruity flavour made out of exotic fruit. The liqueur has a very nice mild taste to it, and is not overpowering in cocktails."}}, {"pk": 21, "model": "djangoshaker.ingredient", "fields": {"name": "Absinthe", "description": "Absinthe is an anise-flavoured spirit derived from herbs, including the flowers and leaves of the herb grande wormwood, together with green anise and sweet fennel. It is commonly green in colour, but can also be colourless.\r\n\r\nThe alcoholic content in absinthe ranges between 45-74%, making it a very strong spirit."}}, {"pk": 22, "model": "djangoshaker.ingredient", "fields": {"name": "Lime juice", "description": "Lime is a citrus-like sour fruit rich in vitamin C. Lime juice is obtained from the fruit by squeezing it out. It is often possible to find lime juice in stores in concentrated form."}}, {"pk": 23, "model": "djangoshaker.ingredient", "fields": {"name": "Lemon juice", "description": "Lemon is a citrus fruit with sour taste. It is very often used in a number of recipes, both for food and cocktails. Lemon juice can be obtained either from a fresh fruit, or from concentrated syrup which can be found in many stores."}}, {"pk": 24, "model": "djangoshaker.ingredient", "fields": {"name": "Sprite", "description": "Sprite is a transparent, lemon-lime flavoured sparkling soft drink. It has a distinct and relatively non-intrusive taste to it. It's commonly used in drinks to add some taste as well as add some nice sparkling effect to a cocktail."}}, {"pk": 25, "model": "djangoshaker.ingredient", "fields": {"name": "Salt", "description": "Salt is one of the most wide-spread additions in cuisine today. It is a mineral composed primarily out of sodium chloride."}}, {"pk": 26, "model": "djangoshaker.ingredient", "fields": {"name": "Mandarin liqueur", "description": "Mandarin liqueur is a variation of orange liqueur made out of mandarins instead of oranges."}}, {"pk": 27, "model": "djangoshaker.ingredient", "fields": {"name": "Banana liqueur", "description": "Banana liqueur is a liqueur made with the flavour of banana. It has a very strong, distinct taste, and it tends to dominate the drinks in which it is used as an ingredient."}}, {"pk": 28, "model": "djangoshaker.ingredient", "fields": {"name": "Coconut liqueur", "description": "Although the main flavour of this liqueur is coconut, it is commonly mixed with passion fruit as well."}}, {"pk": 29, "model": "djangoshaker.ingredient", "fields": {"name": "Blueberry juice", "description": "Blueberry juice is usually a clean type of juice, blue or dark-blue in colour. It is commonly quite sweet and heavy."}}, {"pk": 30, "model": "djangoshaker.ingredient", "fields": {"name": "Cola", "description": "Cola is a carbonated beverage that was typically flavoured by the kola nut as well as vanilla and other flavourings. Today lots of colas are flavoured artificially.\r\n\r\nColas are usually black and translucent.\r\n\r\nThere's a number of different brands currently in the world producing more or less the same type of drink. Some colas are also flavoured with lime, lemon etc."}}, {"pk": 31, "model": "djangoshaker.ingredient", "fields": {"name": "Raspberry", "description": "The raspberry is the edible fruit of a multitude of plant species in the genus Rubus. The name also applies to these plants themselves. Raspberries are perennial, with woody stems. They are a very valued culinary delicacy in most countries, especially due how hard they are to maintain and pick."}}, {"pk": 32, "model": "djangoshaker.ingredient", "fields": {"name": "Cinnamon", "description": "Cinnamon is a spice obtained from the inner bark of several trees from the genus Cinnamomum that is used in both sweet and savoury foods. The cinnamon trees are natively found in South East Asia.\r\n\r\nThe spice is quite strong and flavouring, yet it mixes well with a number of ingredients, giving them a nice hint."}}, {"pk": 33, "model": "djangoshaker.ingredient", "fields": {"name": "Cane sugar", "description": "Cane sugar is obtained from the sugarcane. It is a yellow crystal sugar which is usually much more healthy than the ordinary sugar obtained from sugar beet."}}, {"pk": 34, "model": "djangoshaker.ingredient", "fields": {"name": "Spiced rum", "description": "Spiced rum is commonly based on gold rum, with an addition of various flavours (spices, sometimes caramel). They are usually of a slightly darker golden colour.\r\n\r\nSpices used for flavour can include: cinnamon, rosemary, absinthe/aniseed, or pepper."}}, {"pk": 35, "model": "djangoshaker.ingredient", "fields": {"name": "Orange", "description": "An orange is a citrus fruit cultivated in tropical and subtropical areas. It is commonly found in most grocery stores."}}, {"pk": 36, "model": "djangoshaker.ingredient", "fields": {"name": "Lime", "description": "Lime is a citrus fruit grown in tropical and subtropical areas."}}, {"pk": 37, "model": "djangoshaker.ingredient", "fields": {"name": "Cranberry juice", "description": ""}}, {"pk": 1, "model": "djangoshaker.brand", "fields": {"ingredient": 11, "created": "2011-12-17 14:04:15", "name": "Milk", "modified": "2011-12-17 14:04:15", "description": "Regular cow milk found in most grocery stores."}}, {"pk": 2, "model": "djangoshaker.brand", "fields": {"ingredient": 7, "created": "2011-12-17 14:04:29", "name": "Smirnoff Vodka", "modified": "2011-12-17 14:04:29", "description": "Smirnoff vodka is a very famous type of vodka with high quality. This is, of course, followed by a steep price as well."}}, {"pk": 3, "model": "djangoshaker.brand", "fields": {"ingredient": 7, "created": "2011-12-17 14:04:40", "name": "Wodka Gorbatschow", "modified": "2011-12-17 14:04:40", "description": "Wodka Gorbatschow is a German brand of vodka."}}, {"pk": 4, "model": "djangoshaker.brand", "fields": {"ingredient": 12, "created": "2011-12-17 14:04:53", "name": "Ice", "modified": "2011-12-17 14:04:53", "description": "Plain ice."}}, {"pk": 5, "model": "djangoshaker.brand", "fields": {"ingredient": 10, "created": "2011-12-17 14:05:09", "name": "Kahlua", "modified": "2011-12-17 14:05:09", "description": "Kahlua is a Mexican coffee-flavoured rum-based liqueur. It is dense and sweet, with the distinct taste of coffee, from which it is made. Kahlua also contains sugar, corn syrup and vanilla bean."}}, {"pk": 6, "model": "djangoshaker.brand", "fields": {"ingredient": 10, "created": "2011-12-17 14:05:19", "name": "Kamora", "modified": "2011-12-17 14:05:19", "description": "Kamora is a coffee-based liqueur made in Mexico. It is a bit less sweet than a similar Kahlua liqueur."}}, {"pk": 7, "model": "djangoshaker.brand", "fields": {"ingredient": 20, "created": "2011-12-17 14:05:34", "name": "Marie Brizard Charleston Follies", "modified": "2011-12-17 14:05:34", "description": "Marie Brizard Charleston Follies is a popular brand of passion-fruit liqueur commonly used in a number of different cocktails."}}, {"pk": 8, "model": "djangoshaker.brand", "fields": {"ingredient": 1, "created": "2011-12-17 14:05:44", "name": "Marie Brizard Blue Curacao", "modified": "2011-12-17 14:05:44", "description": "Marie Brizard Blue Curacao is one of the most popular ingredients for cocktails in which it's required to achieve a blue colour."}}, {"pk": 9, "model": "djangoshaker.brand", "fields": {"ingredient": 22, "created": "2011-12-17 14:06:00", "name": "Lime juice", "modified": "2011-12-17 14:06:00", "description": "Regular lime juice which can be found in stores, or squeezed out of fresh limes."}}, {"pk": 10, "model": "djangoshaker.brand", "fields": {"ingredient": 23, "created": "2011-12-17 14:06:16", "name": "Lemon juice", "modified": "2011-12-17 14:06:16", "description": "Regular lemon juice which can be found in stores, or squeezed out of fresh lemons."}}, {"pk": 11, "model": "djangoshaker.brand", "fields": {"ingredient": 24, "created": "2011-12-17 14:06:26", "name": "Sprite", "modified": "2011-12-17 14:06:26", "description": "Sprite is a soft fuzzy drink manufactured by Coca Cola. It is used in many cocktails to add the sparkling effect and a mild test of lemon/lime."}}, {"pk": 12, "model": "djangoshaker.brand", "fields": {"ingredient": 5, "created": "2011-12-17 14:06:35", "name": "Jose Cuervo", "modified": "2011-12-17 14:06:35", "description": "Jose Cuervo is one of the most sold brands of tequila in the world. It is produced by company Tequila Cuervo La Rojena from Mexico."}}, {"pk": 1, "model": "djangoshaker.recipe", "fields": {"name": "Damn Fine in Blue", "created": "2011-12-16 22:13:52", "description": "", "modified": "2011-12-17 10:59:21", "published": true, "instructions": "1. Fill the highball glass with ice.\r\n2. Pour blue curacao, white rum, coconut rum, and orange liqueur into the glass.\r\n3. Stir."}}, {"pk": 2, "model": "djangoshaker.recipe", "fields": {"name": "Kinky Jetza", "created": "2011-12-16 22:18:56", "description": "Kinky Jetza is a nice milky cocktail, very easy to drink due to Irish cream and milk. It reminds of the White Russian cocktail, only with a stronger creamy taste.", "modified": "2011-12-16 22:18:56", "published": true, "instructions": "1. Fill the lowball glass with ice.\r\n2. Pour the Irish cream, amaretto, and coffee liqueur into the glass.\r\n3. Fill the rest of the glass with milk.\r\n4. Stir."}}, {"pk": 3, "model": "djangoshaker.recipe", "fields": {"name": "Red & Black", "created": "2011-12-16 22:21:35", "description": "", "modified": "2011-12-17 10:59:34", "published": true, "instructions": "1. Pour grenadine into a shot glass.\r\n2. Pour the coffee liqueur on top, making sure that it doesn't mix with grenadine."}}, {"pk": 4, "model": "djangoshaker.recipe", "fields": {"name": "The Green Viper", "created": "2011-12-16 22:24:32", "description": "", "modified": "2011-12-17 11:00:00", "published": true, "instructions": "1. Fill a lowball glass with ice.\r\n2. Pour the absinthe, amaretto, passion fruit liqueur, blue curacao, and archers into the glass.\r\n3. Fill the rest of the glass with apple juice.\r\n4. Stir and serve."}}, {"pk": 5, "model": "djangoshaker.recipe", "fields": {"name": "Sunkiss", "created": "2011-12-16 22:28:18", "description": "Sunkiss is a refreshing summer cocktail. The pineapple juice adds the sweetness to the drink, while the orange liqueur and grapefruit juice balance it with bitterness.", "modified": "2011-12-16 22:28:18", "published": true, "instructions": "Yellow grapefruit juice is recommended.\r\n\r\n1. Fill the highball glass with ice.\r\n2. Pour the orange and pineapple juice into the glass.\r\n3. Adjust the amount of pineapple juice and orange juice as preferred.\r\n4. Stir the contents."}}, {"pk": 6, "model": "djangoshaker.recipe", "fields": {"name": "Caress", "created": "2011-12-16 22:30:24", "description": "Caress is a very strong creamy drink with a distinct flavour. The orange liqueur adds for the strength of the cocktail as well for its sweetness.", "modified": "2011-12-16 22:30:24", "published": true, "instructions": "1. Fill the lowball glass with ice.\r\n2. Pour the orange and Irish cream liqueur into the glass.\r\n3. Stir."}}, {"pk": 7, "model": "djangoshaker.recipe", "fields": {"name": "White Russian", "created": "2011-12-16 22:33:09", "description": "White Russian is a classical cocktail both easy to make, and very tasty. It is quite creamy, and easy to drink (and get drunk from).", "modified": "2011-12-16 22:33:09", "published": true, "instructions": "1. Fill a lowball glass with ice.\r\n2. Pour coffee liqueur and vodka into the glass.\r\n3. Fill the rest of the glass with milk.\r\n4. Stir."}}, {"pk": 8, "model": "djangoshaker.recipe", "fields": {"name": "B-52", "created": "2011-12-16 22:37:07", "description": "B-52 is a simple layered shot cocktail, with a very strong taste.", "modified": "2011-12-16 22:37:07", "published": true, "instructions": "1. Pour the coffee liqueur into a shot glass.\r\n2. Pour the Irish cream into the glass, making sure that it doesn't mix with the coffee liqueur.\r\n3. Pour the orange liqueur on top of the glass, making sure that it doesn't mix with the Irish cream beneath (you should have three distinct layers at this point)."}}, {"pk": 9, "model": "djangoshaker.recipe", "fields": {"name": "Monkey Brain", "created": "2011-12-16 22:40:59", "description": "This is one of the most distinct and interesting shot glass cocktails. The texture this cocktail provides to the drinker is something of a unique experience. One can almost be certain that the monkey brain would taste like this provided that it was submerged into the alcohol for a prolonged time period.", "modified": "2011-12-16 22:40:59", "published": true, "instructions": "1. Pour the peach schnapps into a shot glass.\r\n2. Carefully pour the Irish cream on top of the peach schnapps so that the two layers wouldn't mix.\r\n3. Now slowly and carefully drip the grenadine drops into the glass until a brain-like structure gets formed from Irish cream submerged into the peach schnapps.\r\n4. Drink the shot in one go, but don't swallow right away - let the taste and texture spill over your tongue."}}, {"pk": 10, "model": "djangoshaker.recipe", "fields": {"name": "Artlantic", "created": "2011-12-16 22:44:19", "description": "Artlantic is a refreshing sour-sweet cocktail with relatively low percentage of alcohol. The taste has a slight touch of almond (thanks to amaretto). Its colour is blue.", "modified": "2011-12-16 22:44:19", "published": true, "instructions": "1. Fill the highball glass with ice.\r\n2. Pour spiced rum, amaretto, blue curacao, and lime juice into the glass.\r\n3. Fill up the rest of the glass with apple juice.\r\n4. Stir the glass contents and serve."}}, {"pk": 11, "model": "djangoshaker.recipe", "fields": {"name": "Tijuana Taxi", "created": "2011-12-16 22:50:59", "description": "Tijuana Taxi is a nice refreshing drink, perfect for summers, and drinkable in other occasions as well. It has a nice sour taste that fits well with most people.", "modified": "2011-12-16 22:50:59", "published": true, "instructions": "1. Fill the lowball glass with ice.\r\n2. Pour tequila, blue curacao, and lime juice into the glass.\r\n3. Fill up the rest of the glass with Sprite.\r\n4. Stir the glass contents and server."}}, {"pk": 12, "model": "djangoshaker.recipe", "fields": {"name": "TGV", "created": "2011-12-17 08:55:39", "description": "", "modified": "2011-12-17 10:59:54", "published": true, "instructions": "1. Pour tequila, gin, and vodka into a shot glass.\r\n2. Drip the blue curacao into the glass."}}, {"pk": 13, "model": "djangoshaker.recipe", "fields": {"name": "Sky Light", "created": "2011-12-17 09:09:31", "description": "Sky Light is a very nice layered cocktail with pleasant visual appeal.", "modified": "2011-12-17 09:09:31", "published": true, "instructions": "1. Fill a highball glass with ice.\r\n2. Pour vodka into the glass.\r\n3. Slowly add the grenadine.\r\n4. Pour orange juice up to 2/3 of the glass.\r\n5. In a separate glass mix tequila and blue curacao. Carefully pour the mix into the main glass so it sits on top of the vodka/orange juice combination.\r\n6. Garnish the glass with a slice of orange."}}, {"pk": 14, "model": "djangoshaker.recipe", "fields": {"name": "Shamrock shaker", "created": "2011-12-17 09:16:30", "description": "", "modified": "2011-12-17 10:59:46", "published": true, "instructions": "1. Fill the shaker with ice.\r\n2. Pour coffee liqueur, milk, and amaretto into the shaker.\r\n3. Shake vigorously.\r\n4. Strain the contents into cocktail glass.\r\n5. Decorate with edible green and golden edible powder (for cakes)."}}, {"pk": 15, "model": "djangoshaker.recipe", "fields": {"name": "Margarita", "created": "2011-12-17 09:24:25", "description": "One of the most famous cocktails in the world.", "modified": "2011-12-17 09:24:25", "published": true, "instructions": "1. Rub the rim of the cocktail glass with a slice of lime, and then dip the rim into salt.\r\n2. Add some ice into the shaker.\r\n3. Pour tequila, orange liqueur and lime juice into the shaker and shake the contents.\r\n4. Carefully pour the contents of the shaker into the prepared glass (so that salt doesn't get dissolved)."}}, {"pk": 16, "model": "djangoshaker.recipe", "fields": {"name": "Sex on the Beach", "created": "2011-12-17 12:46:11", "description": "", "modified": "2011-12-17 12:46:11", "published": true, "instructions": "1. Fill a highball glass with ice.\r\n2. Pour vodka, peach schnapps, and cranberry juice into the glass.\r\n3. Top the glass with orange juice.\r\n4. Stir"}}, {"pk": 17, "model": "djangoshaker.recipe", "fields": {"name": "B-53", "created": "2011-12-17 12:48:21", "description": "", "modified": "2011-12-17 12:48:21", "published": true, "instructions": "1. Pour coffee liqueur into the into a shot glass.\r\n2. Pour Irish cream so that it doesn't get mixed with the coffee liqueur.\r\n3. Pour absinthe on top so that it doesn't get mixed with the Irish cream."}}, {"pk": 18, "model": "djangoshaker.recipe", "fields": {"name": "Daiquiri", "created": "2011-12-17 13:38:01", "description": "", "modified": "2011-12-17 13:38:01", "published": true, "instructions": "1. Stir lime juice and sugar in a shaker until it dissolves.\r\n2. Add rum and the ice cubes, and shake vigorously for about 20 seconds.\r\n3. Strain the contents into a cocktail glass.\r\n4. Drop in a lime slice into the glass."}}, {"pk": 19, "model": "djangoshaker.recipe", "fields": {"name": "Boardwalk Breeze", "created": "2011-12-17 13:41:13", "description": "", "modified": "2011-12-17 13:41:13", "published": true, "instructions": "1. Pour all the ingredients into a shaker.\r\n2. Shake vigorously, then strain into a highball glass."}}, {"pk": 20, "model": "djangoshaker.recipe", "fields": {"name": "Lewie", "created": "2011-12-17 13:43:51", "description": "", "modified": "2011-12-17 13:43:51", "published": true, "instructions": "1. Fill a pilsner glass with ice.\r\n2. Add the rum, amaretto, and orange juice and stir.\r\n3. Top with the splash of grenadine."}}, {"pk": 21, "model": "djangoshaker.recipe", "fields": {"name": "The Olie", "created": "2011-12-17 13:52:48", "description": "", "modified": "2011-12-17 13:52:48", "published": true, "instructions": "1. Pour vodka, white rum, and tequila into a shot glass.\r\n2. Top off with a splash of lemonade."}}, {"pk": 22, "model": "djangoshaker.recipe", "fields": {"name": "Bass Player", "created": "2011-12-17 13:55:22", "description": "", "modified": "2011-12-17 13:55:22", "published": true, "instructions": "1. Pour vodka, orange liqueur, and orange juice into the glass.\r\n2. Top with a dash of grenadine."}}, {"pk": 23, "model": "djangoshaker.recipe", "fields": {"name": "BTC", "created": "2011-12-17 13:58:58", "description": "", "modified": "2011-12-17 13:58:58", "published": true, "instructions": "1. Pour tequila into and orange liqueur into a shot glass.\r\n2. Slowly pour Irish cream on top in order to get a layer floating at top."}}, {"pk": 1, "model": "djangoshaker.recipeingredient", "fields": {"amount": 4.5, "measure": 1, "recipe": 1, "ingredient": 1}}, {"pk": 2, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 1, "ingredient": 2}}, {"pk": 3, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 1, "ingredient": 3}}, {"pk": 4, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 1, "ingredient": 4}}, {"pk": 5, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 2, "ingredient": 8}}, {"pk": 6, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 2, "ingredient": 9}}, {"pk": 7, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 2, "ingredient": 10}}, {"pk": 8, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 2, "ingredient": 11}}, {"pk": 9, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 3, "ingredient": 14}}, {"pk": 10, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 3, "ingredient": 10}}, {"pk": 11, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.3, "measure": 1, "recipe": 4, "ingredient": 21}}, {"pk": 12, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.3, "measure": 1, "recipe": 4, "ingredient": 9}}, {"pk": 13, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.3, "measure": 1, "recipe": 4, "ingredient": 20}}, {"pk": 14, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.3, "measure": 1, "recipe": 4, "ingredient": 1}}, {"pk": 15, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.3, "measure": 1, "recipe": 4, "ingredient": 19}}, {"pk": 16, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 4, "ingredient": 12}}, {"pk": 17, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 4, "ingredient": 18}}, {"pk": 18, "model": "djangoshaker.recipeingredient", "fields": {"amount": 4.0, "measure": 1, "recipe": 5, "ingredient": 4}}, {"pk": 19, "model": "djangoshaker.recipeingredient", "fields": {"amount": 5.0, "measure": 1, "recipe": 5, "ingredient": 17}}, {"pk": 20, "model": "djangoshaker.recipeingredient", "fields": {"amount": 3.0, "measure": 1, "recipe": 5, "ingredient": 16}}, {"pk": 21, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 5, "ingredient": 12}}, {"pk": 22, "model": "djangoshaker.recipeingredient", "fields": {"amount": 5.0, "measure": 1, "recipe": 6, "ingredient": 4}}, {"pk": 23, "model": "djangoshaker.recipeingredient", "fields": {"amount": 5.0, "measure": 1, "recipe": 6, "ingredient": 8}}, {"pk": 24, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 6, "ingredient": 12}}, {"pk": 25, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.5, "measure": 1, "recipe": 7, "ingredient": 7}}, {"pk": 26, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.5, "measure": 1, "recipe": 7, "ingredient": 10}}, {"pk": 27, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 7, "ingredient": 11}}, {"pk": 28, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 7, "ingredient": 12}}, {"pk": 29, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 8, "ingredient": 10}}, {"pk": 30, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 8, "ingredient": 8}}, {"pk": 31, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 8, "ingredient": 4}}, {"pk": 32, "model": "djangoshaker.recipeingredient", "fields": {"amount": 3.0, "measure": 1, "recipe": 9, "ingredient": 19}}, {"pk": 33, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 7, "recipe": 9, "ingredient": 8}}, {"pk": 34, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 6, "recipe": 9, "ingredient": 14}}, {"pk": 35, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 10, "ingredient": 34}}, {"pk": 36, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 10, "ingredient": 9}}, {"pk": 37, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 10, "ingredient": 1}}, {"pk": 38, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 10, "ingredient": 22}}, {"pk": 39, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 10, "ingredient": 18}}, {"pk": 40, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 10, "ingredient": 12}}, {"pk": 41, "model": "djangoshaker.recipeingredient", "fields": {"amount": 6.0, "measure": 1, "recipe": 11, "ingredient": 5}}, {"pk": 42, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 11, "ingredient": 1}}, {"pk": 43, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 11, "ingredient": 22}}, {"pk": 44, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 11, "ingredient": 24}}, {"pk": 45, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 11, "ingredient": 12}}, {"pk": 46, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 1, "ingredient": 12}}, {"pk": 47, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 12, "ingredient": 5}}, {"pk": 48, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 12, "ingredient": 6}}, {"pk": 49, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 12, "ingredient": 7}}, {"pk": 50, "model": "djangoshaker.recipeingredient", "fields": {"amount": 10.0, "measure": 6, "recipe": 12, "ingredient": 1}}, {"pk": 51, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 13, "ingredient": 1}}, {"pk": 52, "model": "djangoshaker.recipeingredient", "fields": {"amount": 3.0, "measure": 1, "recipe": 13, "ingredient": 5}}, {"pk": 53, "model": "djangoshaker.recipeingredient", "fields": {"amount": 4.0, "measure": 1, "recipe": 13, "ingredient": 7}}, {"pk": 54, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 13, "ingredient": 14}}, {"pk": 55, "model": "djangoshaker.recipeingredient", "fields": {"amount": 9.0, "measure": 1, "recipe": 13, "ingredient": 13}}, {"pk": 56, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 8, "recipe": 13, "ingredient": 35}}, {"pk": 57, "model": "djangoshaker.recipeingredient", "fields": {"amount": 4.5, "measure": 1, "recipe": 14, "ingredient": 10}}, {"pk": 58, "model": "djangoshaker.recipeingredient", "fields": {"amount": 7.5, "measure": 1, "recipe": 14, "ingredient": 11}}, {"pk": 59, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 14, "ingredient": 9}}, {"pk": 60, "model": "djangoshaker.recipeingredient", "fields": {"amount": 3.5, "measure": 1, "recipe": 15, "ingredient": 5}}, {"pk": 61, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 15, "ingredient": 4}}, {"pk": 62, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.5, "measure": 1, "recipe": 15, "ingredient": 22}}, {"pk": 63, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 8, "recipe": 15, "ingredient": 36}}, {"pk": 64, "model": "djangoshaker.recipeingredient", "fields": {"amount": 5.0, "measure": 5, "recipe": 15, "ingredient": 25}}, {"pk": 65, "model": "djangoshaker.recipeingredient", "fields": {"amount": 4.0, "measure": 1, "recipe": 16, "ingredient": 7}}, {"pk": 66, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 16, "ingredient": 19}}, {"pk": 67, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 16, "ingredient": 37}}, {"pk": 68, "model": "djangoshaker.recipeingredient", "fields": {"amount": 4.0, "measure": 1, "recipe": 16, "ingredient": 13}}, {"pk": 69, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 17, "ingredient": 10}}, {"pk": 70, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 17, "ingredient": 8}}, {"pk": 71, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 17, "ingredient": 21}}, {"pk": 72, "model": "djangoshaker.recipeingredient", "fields": {"amount": 3.0, "measure": 1, "recipe": 18, "ingredient": 22}}, {"pk": 73, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 7, "recipe": 18, "ingredient": 33}}, {"pk": 74, "model": "djangoshaker.recipeingredient", "fields": {"amount": 6.0, "measure": 1, "recipe": 18, "ingredient": 2}}, {"pk": 75, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 18, "ingredient": 12}}, {"pk": 76, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 8, "recipe": 18, "ingredient": 36}}, {"pk": 77, "model": "djangoshaker.recipeingredient", "fields": {"amount": 4.5, "measure": 1, "recipe": 19, "ingredient": 3}}, {"pk": 78, "model": "djangoshaker.recipeingredient", "fields": {"amount": 4.5, "measure": 1, "recipe": 19, "ingredient": 9}}, {"pk": 79, "model": "djangoshaker.recipeingredient", "fields": {"amount": 8.0, "measure": 1, "recipe": 19, "ingredient": 13}}, {"pk": 80, "model": "djangoshaker.recipeingredient", "fields": {"amount": 8.0, "measure": 1, "recipe": 19, "ingredient": 17}}, {"pk": 81, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 9, "recipe": 19, "ingredient": 14}}, {"pk": 82, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 4, "recipe": 19, "ingredient": 12}}, {"pk": 83, "model": "djangoshaker.recipeingredient", "fields": {"amount": 6.0, "measure": 1, "recipe": 20, "ingredient": 34}}, {"pk": 84, "model": "djangoshaker.recipeingredient", "fields": {"amount": 3.0, "measure": 1, "recipe": 20, "ingredient": 9}}, {"pk": 85, "model": "djangoshaker.recipeingredient", "fields": {"amount": 8.0, "measure": 1, "recipe": 20, "ingredient": 13}}, {"pk": 86, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 10, "recipe": 20, "ingredient": 14}}, {"pk": 87, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 21, "ingredient": 7}}, {"pk": 88, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 21, "ingredient": 2}}, {"pk": 89, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 21, "ingredient": 5}}, {"pk": 90, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 10, "recipe": 21, "ingredient": 15}}, {"pk": 91, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 22, "ingredient": 7}}, {"pk": 92, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 22, "ingredient": 4}}, {"pk": 93, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 1, "recipe": 22, "ingredient": 13}}, {"pk": 94, "model": "djangoshaker.recipeingredient", "fields": {"amount": 1.0, "measure": 9, "recipe": 22, "ingredient": 14}}, {"pk": 95, "model": "djangoshaker.recipeingredient", "fields": {"amount": 0.5, "measure": 1, "recipe": 23, "ingredient": 5}}, {"pk": 96, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 23, "ingredient": 4}}, {"pk": 97, "model": "djangoshaker.recipeingredient", "fields": {"amount": 2.0, "measure": 1, "recipe": 23, "ingredient": 8}}] \ No newline at end of file diff --git a/djangoshaker/migrations/0001_initial.py b/djangoshaker/migrations/0001_initial.py new file mode 100644 --- /dev/null +++ b/djangoshaker/migrations/0001_initial.py @@ -0,0 +1,142 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Measure' + db.create_table('djangoshaker_measure', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)), + ('marking', self.gf('django.db.models.fields.CharField')(unique=True, max_length=24)), + ('description', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('djangoshaker', ['Measure']) + + # Adding model 'Ingredient' + db.create_table('djangoshaker_ingredient', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)), + ('description', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('djangoshaker', ['Ingredient']) + + # Adding model 'Brand' + db.create_table('djangoshaker_brand', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)), + ('description', self.gf('django.db.models.fields.TextField')()), + ('ingredient', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['djangoshaker.Ingredient'])), + ('created', self.gf('django.db.models.fields.DateTimeField')()), + ('modified', self.gf('django.db.models.fields.DateTimeField')()), + )) + db.send_create_signal('djangoshaker', ['Brand']) + + # Adding model 'MeasureConversion' + db.create_table('djangoshaker_measureconversion', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('from_measure', self.gf('django.db.models.fields.related.ForeignKey')(related_name='conversion_from', to=orm['djangoshaker.Measure'])), + ('to_measure', self.gf('django.db.models.fields.related.ForeignKey')(related_name='conversion_to', to=orm['djangoshaker.Measure'])), + ('ratio', self.gf('django.db.models.fields.FloatField')()), + )) + db.send_create_signal('djangoshaker', ['MeasureConversion']) + + # Adding model 'Recipe' + db.create_table('djangoshaker_recipe', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)), + ('description', self.gf('django.db.models.fields.TextField')()), + ('instructions', self.gf('django.db.models.fields.TextField')()), + ('created', self.gf('django.db.models.fields.DateTimeField')()), + ('modified', self.gf('django.db.models.fields.DateTimeField')()), + ('published', self.gf('django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('djangoshaker', ['Recipe']) + + # Adding model 'RecipeIngredient' + db.create_table('djangoshaker_recipeingredient', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('recipe', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['djangoshaker.Recipe'])), + ('ingredient', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['djangoshaker.Ingredient'])), + ('amount', self.gf('django.db.models.fields.FloatField')()), + ('measure', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['djangoshaker.Measure'])), + )) + db.send_create_signal('djangoshaker', ['RecipeIngredient']) + + + def backwards(self, orm): + + # Deleting model 'Measure' + db.delete_table('djangoshaker_measure') + + # Deleting model 'Ingredient' + db.delete_table('djangoshaker_ingredient') + + # Deleting model 'Brand' + db.delete_table('djangoshaker_brand') + + # Deleting model 'MeasureConversion' + db.delete_table('djangoshaker_measureconversion') + + # Deleting model 'Recipe' + db.delete_table('djangoshaker_recipe') + + # Deleting model 'RecipeIngredient' + db.delete_table('djangoshaker_recipeingredient') + + + models = { + 'djangoshaker.brand': { + 'Meta': {'object_name': 'Brand'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ingredient': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangoshaker.Ingredient']"}), + 'modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) + }, + 'djangoshaker.ingredient': { + 'Meta': {'object_name': 'Ingredient'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}) + }, + 'djangoshaker.measure': { + 'Meta': {'object_name': 'Measure'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'marking': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '24'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}) + }, + 'djangoshaker.measureconversion': { + 'Meta': {'object_name': 'MeasureConversion'}, + 'from_measure': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conversion_from'", 'to': "orm['djangoshaker.Measure']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ratio': ('django.db.models.fields.FloatField', [], {}), + 'to_measure': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conversion_to'", 'to': "orm['djangoshaker.Measure']"}) + }, + 'djangoshaker.recipe': { + 'Meta': {'object_name': 'Recipe'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instructions': ('django.db.models.fields.TextField', [], {}), + 'modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'djangoshaker.recipeingredient': { + 'Meta': {'object_name': 'RecipeIngredient'}, + 'amount': ('django.db.models.fields.FloatField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ingredient': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangoshaker.Ingredient']"}), + 'measure': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangoshaker.Measure']"}), + 'recipe': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djangoshaker.Recipe']"}) + } + } + + complete_apps = ['djangoshaker'] diff --git a/djangoshaker/migrations/__init__.py b/djangoshaker/migrations/__init__.py new file mode 100644 diff --git a/djangoshaker/models.py b/djangoshaker/models.py new file mode 100644 --- /dev/null +++ b/djangoshaker/models.py @@ -0,0 +1,313 @@ +from django.db import models +from django.db.models import F + +import datetime + +class Measure(models.Model): + """ + Models a measure. A measure is anything which can be used to represent some + amount of ingredient, for example - pinch, litre, pound, ounce. + + Some measures are also convertible to others through the MeasureConversion + module. + + Fields: + + name - Name of measure. E.g. 'litre'. + + marking - A short marking used for the measure. E.g. 'l'. + + description - Measure description. + """ + + name = models.CharField(max_length = 64, unique = True) + marking = models.CharField(max_length = 24, unique = True) + description = models.TextField(blank = True) + + def __unicode__(self): + """ + Returns unicode representation of a measure. + """ + + return "%s (%s)" % (self.name, self.marking) + +class Ingredient(models.Model): + """ + Models an ingredient of a recipe. Ingredient is not bound to a particular + brand. + + Fields: + + name - Ingredient name. E.g. 'white rum', 'orange juice'. + + description - Ingredient description. + """ + + name = models.CharField(max_length = 64, unique = True) + description = models.TextField(blank = True) + + def __unicode__(self): + """ + Returns a unicode representation of an ingredient. + """ + + return self.name + +class Brand(models.Model): + """ + Models a brand of some ingredient. The brand can be either specific and + actually corresponding to some particular company, or generic, like tap + water or milk. + + Fields: + + name - Name of a brand. E.g. 'Milton Milk'. + + description - Brand description. + + ingredient - Foreign key to an ingredient that this brand provides. + + created - Date when the brand was created in the database. + + modified - Last date of change of the brand. + """ + + name = models.CharField(max_length = 100, unique = True) + description = models.TextField(blank = True) + ingredient = models.ForeignKey(Ingredient) + created = models.DateTimeField(editable = False) + modified = models.DateTimeField(editable = False) + + def save(self, *args, **kwargs): + """ + Overrides the default save operation in order to properly take care of + the created and modified fields (they should be set to same value on + initial creation of an object). + """ + + cur_date = datetime.datetime.today() + + if not self.id: + self.created = cur_date + + self.modified = cur_date + + super(Brand, self).save(*args, **kwargs) + + def __unicode__(self): + """ + Returns a unicode representation of a brand. + """ + + return "%s (%s)" % (self.name, self.ingredient.name) + + + @staticmethod + def latest_additions(count=5): + """ + Returns a queryset of last added brands. + + Arguments: + + count - Number of last added entries to return. Default is 5. + """ + + return Brand.objects.order_by('-created')[:count] + + @staticmethod + def latest_changes(count=5): + """ + Returns a queryset of last modified brands. + + Arguments: + + count - Number of last modified entries to return. Default is 5. + """ + + return Brand.objects.filter(modified__gt=F('created')).order_by('-modified')[:count] + +class MeasureConversion(models.Model): + """ + Models conversion ratios between different measures. Allows the user to + select related measures when presenting recipes. + + Fields: + + from_measure - Foreign key to a measure from which the conversion is made. + + to_measure - Foreign key to a measure to which the conversion is made. + + ratio - A number by which the from_measure is multiplied with in order to + get the amount in to_measure. + + @TODO: Implement some checks that conversions aren't added in both (a, b) + and (b, a). + """ + + from_measure = models.ForeignKey(Measure, related_name = 'conversion_from') + to_measure = models.ForeignKey(Measure, related_name = 'conversion_to') + ratio = models.FloatField() + + def __unicode__(self): + """ + Returns a unicode representation of measure conversion. + """ + + return "1 %s amounts to %d %s" % (self.from_measure.marking, self.ratio, self.to_measure.marking) + +class Recipe(models.Model): + """ + Models basic recipe information. The recipe ingredients are kept in a + separate model. + + Fields: + + name - Name of a recipe. + + description - Description of a recipe. This is not instructions. Those are + kept in a separate field. + + instructions - Instructions for recipe preparation. + + created - Date when the recipe was created in the database. + + modified - Last date of change of the recipe. + + published - Keeps track whether the recipe has been published or + not. Unpublished recipes are not shown in any list, and their modification + time is kept same as the creatio one. + + ingredients = models.ManyToMany(Ingredient, through = "RecipeIngredients") + """ + + name = models.CharField(max_length = 128, unique = True) + description = models.TextField(blank = True) + instructions = models.TextField() + created = models.DateTimeField(editable = False) + modified = models.DateTimeField(editable = False) + published = models.BooleanField(default = False, editable = False) + + def __init__(self, *args, **kwargs): + """ + Overriden constructor that allows the instance to remember its original + value for the published field. + """ + + super(Recipe, self).__init__(*args, **kwargs) + self.__original_published = self.published + + def __unicode__(self): + """ + Returns a unicode representation of a recipe. + + @TODO: See what the preferred output might be for this one. Currently + listing all ingredients. + """ + + result = self.name + "(" + for ing in self.recipeingredient_set.all(): + result += str(ing) + ";" + result += ")" + return result + + def publish(self): + """ + Publishes the recipe. + """ + + self.published = True + self.save() + + def save(self, *args, **kwargs): + """ + Overrides the default save operation in order to properly take care of + the created and modified fields (they should be set to same value on + initial creation of an object). + + It also takes care of updating the modified data _only_ while the recipe + is published. + """ + + cur_date = datetime.datetime.today() + + if not self.id: + self.created = cur_date + self.modified = cur_date + elif self.published and self.__original_published: + self.modified = cur_date + elif not self.published and self.__original_published: + raise Exception("Unpublishing is not permitted.") + + super(Recipe, self).save(*args, **kwargs) + + def is_published(self): + """ + Returns whether the recipe was published or not. + """ + + return self.published + + published.short_description = "Published?" + + @staticmethod + def latest_additions(count=5): + """ + Returns a queryset of last added recipes. + + Arguments: + + count - Number of last added recipes to return. Default is 5. + """ + + return Recipe.objects.filter(published = True).order_by('-created')[:count] + + @staticmethod + def latest_changes(count=5): + """ + Returns a queryset of last modified recipes. + + Arguments: + + count - Number of last modified entries to return. Default is 5. + """ + + return Recipe.objects.filter(modified__gt=F('created'), published = True).order_by('-modified')[:count] + +class RecipeIngredient(models.Model): + """ + Models the recipe ingredients. Connects the actualy recipe to a number of + different ingredients. + + Fields: + + recipe - Foreign key to which an ingredient is assigned. + + ingredient - Foreign key to ingredient being used in a recipe. + + amount - Quantity of ingredient required for the recipe. + """ + + recipe = models.ForeignKey(Recipe) + ingredient = models.ForeignKey(Ingredient) + amount = models.FloatField() + measure = models.ForeignKey(Measure) + + def __unicode__(self): + """ + Returns a unicode representation of an ingredient for the recipe + (stating which ingredient, how much of it etc). + """ + + return "%d %s of %s" % (self.amount, self.measure.marking, self.ingredient) + + def save(self, *args, **kwargs): + """ + Overrides the default save method to update the modification time of a + parent recipe (if applicable). + """ + + if self.recipe.published: + self.recipe.save() + + super(RecipeIngredient, self).save(*args, **kwargs) + diff --git a/djangoshaker/static/colour.css b/djangoshaker/static/colour.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/colour.css @@ -0,0 +1,2 @@ +@import url("/static/mobile/colour.css") screen and (max-width: 800px); +@import url("/static/desktop/colour.css") screen and (min-width: 801px); diff --git a/djangoshaker/static/desktop/colour.css b/djangoshaker/static/desktop/colour.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/desktop/colour.css @@ -0,0 +1,24 @@ +/* + http://colorschemedesigner.com/#1921Tk.--w0w0 + */ + +html { + background-color: #FFE7A5; +} + +#header, #content, #left_sidebar_wrapper { + background-color: #B2B6FF; +} + +.title_section { + background-color: #969BFF; +} + +.listing_link:link, .listing_link:visited, .menu_link:link, .menu_link:visited { + color: #252CA6; +} + +.listing_link:active, .listing_link:hover, .menu_link:active, .menu_link:hover { + color: #FFE7A5; + background-color: #252CA6; +} \ No newline at end of file diff --git a/djangoshaker/static/desktop/layout.css b/djangoshaker/static/desktop/layout.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/desktop/layout.css @@ -0,0 +1,37 @@ +html { + width: 80%; + margin: 0 10% 0 10%; +} + +body { + margin:0; +} + +#header { + padding-left: 10px; + padding-right: 10px; + margin: 0; +} + +#content { + width: 70%; + float: left; + padding: 0; +} + +.title_section { + padding: 5px 10px 5px 10px; +} + +#left_sidebar_wrapper { + width: 20%; + float: right; +} + +.title_section { + margin-top: 0; +} + +.text_long { + padding: 0px 10px; +} \ No newline at end of file diff --git a/djangoshaker/static/desktop/style.css b/djangoshaker/static/desktop/style.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/desktop/style.css @@ -0,0 +1,15 @@ +.listing { + list-style: none; + padding: 0; + margin: 10px; +} + +.listing_link { + text-decoration: none; + overflow: hidden; +} + +.menu_link { + overflow: hidden; + text-decoration: none; +} \ No newline at end of file diff --git a/djangoshaker/static/layout.css b/djangoshaker/static/layout.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/layout.css @@ -0,0 +1,2 @@ +@import url("/static/mobile/layout.css") screen and (max-width: 800px); +@import url("/static/desktop/layout.css") screen and (min-width: 801px); diff --git a/djangoshaker/static/mobile/colour.css b/djangoshaker/static/mobile/colour.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/mobile/colour.css @@ -0,0 +1,46 @@ +/* + http://colorschemedesigner.com/#1921Tk.--w0w0 + */ + +.title_section { + background-color: #969BFF; +} + +body { + background-color: #B2B6FF; +} + +.listing { + background-color: #FFE7A5; +} + +.listing li.item { + border-color: #A6821E; +} + +.listing_link:link, .listing_link:visited { + color: #A6821E; +} + +.listing_link:active, .listing_link:hover { + color: #FFE7A5; + background-color: #A6821E; +} + +.menu_item { + background-color: #969BFF; +} + +.menu_link { + border-color: #252CA6; +} + +.menu_link:link, .menu_link:visited { + color: #252CA6; +} + +.menu_link:active, .menu_link:hover { + color: #969BFF; + background-color: #252CA6; +} + diff --git a/djangoshaker/static/mobile/layout.css b/djangoshaker/static/mobile/layout.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/mobile/layout.css @@ -0,0 +1,42 @@ +body { + margin: 0px; +} + +.title_main, .title_section { + margin: 0px; + padding-top: 10px; + padding-bottom: 10px; +} + +.title_main, .title_section, .listing li.item { + padding-left: 5px; + padding-right: 5px; + +} + +.listing { + padding: 0px; + margin: 0px; +} + +.listing_link { + display: block; + width: 100%; + height: 2em; + line-height: 2em; + padding: 0px; +} + +.menu_item { + display: block; + float: left; + width: 100%; +} + +.menu_link { + display: block; + width: 100%; + height: 2em; + line-height: 2em; +} + diff --git a/djangoshaker/static/mobile/style.css b/djangoshaker/static/mobile/style.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/mobile/style.css @@ -0,0 +1,25 @@ +.listing { + list-style: none; +} + +.listing li.item { + border-bottom: 2px dotted; +} + +.listing li:last-child { + border-bottom: none; +} + +.listing_link { + text-decoration: none; + overflow: hidden; + font-size: 1.2em; +} + +.menu_link { + text-decoration: none; + text-align: center; + border-bottom: 2px dotted; + font-size: 1.2em; +} + diff --git a/djangoshaker/static/style.css b/djangoshaker/static/style.css new file mode 100644 --- /dev/null +++ b/djangoshaker/static/style.css @@ -0,0 +1,2 @@ +@import url("/static/mobile/style.css") screen and (max-width: 800px); +@import url("/static/desktop/style.css") screen and (min-width: 801px); diff --git a/djangoshaker/templates/djangoshaker/brand.html b/djangoshaker/templates/djangoshaker/brand.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/brand.html @@ -0,0 +1,23 @@ +{% extends "djangoshaker/template.html" %} + +{% load djangoshaker_html_helpers %} + +{% block title %}Django Shaker - {{brand.name}}{% endblock %} + +{% block header_title %}{{brand.name}}{% endblock %} + +{% block content %} + +{% if brand %} +{% if brand.description %} +

Description

+
{{brand.description|linebreaks}}
+{% endif %} +

Ingredient Type

+ +{% endif %} + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/brand_list.html b/djangoshaker/templates/djangoshaker/brand_list.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/brand_list.html @@ -0,0 +1,20 @@ +{% extends "djangoshaker/template.html" %} + +{% load djangoshaker_html_helpers %} + +{% block title %}Django Shaker - Brands{% endblock %} + +{% block header_title %}Brands{% endblock %} + +{% block content %} + +{% if brands %} + +{% endif %} + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/index.html b/djangoshaker/templates/djangoshaker/index.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/index.html @@ -0,0 +1,48 @@ +{% extends "djangoshaker/template.html" %} + +{% load djangoshaker_html_helpers %} + +{% block title %}Django Shaker{% endblock %} + +{% block header_title %}Django Shaker{% endblock %} + +{% block content %} + +{% if recipes_added %} +

Recently added recipes

+ +{% endif %} + +{% if recipes_modified %} +

Recently modified recipes

+ +{% endif %} + +{% if brands_added %} +

Recently added brands

+ +{% endif %} + +{% if brands_modified %} +

Recently modified brands

+ +{% endif %} + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/ingredient.html b/djangoshaker/templates/djangoshaker/ingredient.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/ingredient.html @@ -0,0 +1,30 @@ +{% extends "djangoshaker/template.html" %} + +{% load djangoshaker_html_helpers %} + +{% block title %}Django Shaker - {{ingredient.name}}{% endblock %} + +{% block header_title %}{{ingredient.name}}{% endblock %} + +{% block content %} + +{% if ingredient %} +

Description

+ +{% if ingredient.description %} +
{{ingredient.description|linebreaks}}
+{% endif %} + +{% if ingredient.brand_set.all %} +

Available Brands

+ +{% endif %} + +{% endif %} + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/ingredient_list.html b/djangoshaker/templates/djangoshaker/ingredient_list.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/ingredient_list.html @@ -0,0 +1,20 @@ +{% extends "djangoshaker/template.html" %} + +{% load djangoshaker_html_helpers %} + +{% block title %}Django Shaker - Ingredients{% endblock %} + +{% block header_title %}Ingredients{% endblock %} + +{% block content %} + +{% if ingredients %} + +{% endif %} + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/link.html b/djangoshaker/templates/djangoshaker/link.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/link.html @@ -0,0 +1,7 @@ +{% load url from future %} + +{% if identifier %} +{{title}} +{% else %} +{{title}} +{% endif %} diff --git a/djangoshaker/templates/djangoshaker/measure.html b/djangoshaker/templates/djangoshaker/measure.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/measure.html @@ -0,0 +1,22 @@ +{% extends "djangoshaker/template.html" %} +{% block title %}{{measure.name}}{% endblock %} + +{% block heading_title %}{{measure.name}}{% endblock %} + + +{% block content %} +

Marking

+ +
+ {{measure.marking}} +
+ +{% if measure.description %} +

Description

+
+ {{measure.description}} +
+{% endif %} + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/measure_list.html b/djangoshaker/templates/djangoshaker/measure_list.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/measure_list.html @@ -0,0 +1,18 @@ +{% extends "djangoshaker/template.html" %} + +{% load djangoshaker_html_helpers %} + +{% block title %}Django Shaker - Measure List {% endblock %} + +{% block header_title %}Measure List{% endblock %} + +{% block content %} + + + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/recipe.html b/djangoshaker/templates/djangoshaker/recipe.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/recipe.html @@ -0,0 +1,38 @@ +{% extends "djangoshaker/template.html" %} + +{% load djangoshaker_html_helpers %} + +{% block title %}Django Shaker - {{recipe.name}}{% endblock %} + +{% block header_title %}{{recipe.name}}{% endblock %} + +{% block content %} + +{% if recipe %} + +

Ingredients

+ +{% if recipe.recipeingredient_set %} + +{% endif %} + +

Preparation Instructions

+
+ {{recipe.instructions|linebreaks}} +
+ +{% if recipe.description %} +

Description

+
+ {{recipe.description|linebreaks}} +
+{% endif %} + +{% endif %} + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/recipe_list.html b/djangoshaker/templates/djangoshaker/recipe_list.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/recipe_list.html @@ -0,0 +1,20 @@ +{% extends "djangoshaker/template.html" %} + +{% load djangoshaker_html_helpers %} + +{% block title %}Django Shaker - Recipes{% endblock %} + +{% block header_title %}Recipes{% endblock %} + +{% block content %} + +{% if recipes %} + +{% endif %} + +{% endblock %} + diff --git a/djangoshaker/templates/djangoshaker/template.html b/djangoshaker/templates/djangoshaker/template.html new file mode 100644 --- /dev/null +++ b/djangoshaker/templates/djangoshaker/template.html @@ -0,0 +1,39 @@ +{% load djangoshaker_html_helpers %} + + + + + {% block title %}Django Shaker{% endblock %} + + + + + + + + + + +
+ {% block content %} + {% endblock %} +
+ + + + + + diff --git a/djangoshaker/templatetags/__init__.py b/djangoshaker/templatetags/__init__.py new file mode 100644 diff --git a/djangoshaker/templatetags/djangoshaker_html_helpers.py b/djangoshaker/templatetags/djangoshaker_html_helpers.py new file mode 100644 --- /dev/null +++ b/djangoshaker/templatetags/djangoshaker_html_helpers.py @@ -0,0 +1,22 @@ +from django import template + +register = template.Library() + +@register.inclusion_tag('djangoshaker/link.html') +def html_link(view, title, identifier = None, html_class = None): + ''' + A small wrapper for showing HTML links. + + Arguments: + + view - View name for which the URL should be shown. + + title - Title which will be shown as the link. + + identifier - Identifier which is passed to the view for processing. + + html_class - Class which should be assigned to the link. + ''' + + return {'view': view, 'identifier': identifier, 'title': title, 'html_class': html_class} + diff --git a/djangoshaker/tests.py b/djangoshaker/tests.py new file mode 100644 --- /dev/null +++ b/djangoshaker/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/djangoshaker/urls.py b/djangoshaker/urls.py new file mode 100644 --- /dev/null +++ b/djangoshaker/urls.py @@ -0,0 +1,48 @@ +from django.conf.urls.defaults import * +from django.views.generic import DetailView, ListView +from djangoshaker.models import Brand, Recipe, Ingredient, Measure +from djangoshaker.views import IndexView + +urlpatterns = patterns('djangoshaker.views', + url(r'^$', IndexView.as_view(), name="index"), + + url(r'^recipe/$', ListView.as_view(queryset = Recipe.objects.filter(published = True).order_by("name"), + template_name = 'djangoshaker/recipe_list.html', + context_object_name = 'recipes'), + name='recipe_list'), + + url(r'^brand/$', ListView.as_view(model = Brand, + queryset = Brand.objects.all().order_by("name"), + template_name = 'djangoshaker/brand_list.html', + context_object_name = 'brands'), + name="brand_list"), + + url(r'^ingredient/$', ListView.as_view(model = Ingredient, + queryset = Ingredient.objects.all().order_by("name"), + template_name = 'djangoshaker/ingredient_list.html', + context_object_name = 'ingredients'), + name="ingredient_list"), + + url(r'^measure/$', ListView.as_view(model = Measure, + queryset = Measure.objects.all().order_by("name"), + template_name = 'djangoshaker/measure_list.html', + context_object_name = 'measures'), + name="measure_list"), + + url(r'^brand/(?P\d+)$', DetailView.as_view(model = Brand, + template_name = 'djangoshaker/brand.html'), + name="brand"), + + url(r'^recipe/(?P\d+)$', DetailView.as_view(model = Recipe, + template_name = 'djangoshaker/recipe.html'), + name="recipe"), + + url(r'^ingredient/(?P\d+)$', DetailView.as_view(model = Ingredient, + template_name = 'djangoshaker/ingredient.html'), + name="ingredient"), + + url(r'^measure/(?P\d+)$', DetailView.as_view(model = Measure, + template_name = 'djangoshaker/measure.html'), + name="measure"), + ) + diff --git a/djangoshaker/views.py b/djangoshaker/views.py new file mode 100644 --- /dev/null +++ b/djangoshaker/views.py @@ -0,0 +1,38 @@ +from django.views.generic import TemplateView + +from djangoshaker.models import Brand, Recipe + +class IndexView(TemplateView): + """ + Custom view used for rendering the index page. + + Contexts provided: + + recipes_added - List of last added recipes. + + recipes_modified - List of last modified recipes. + + brands_added - List of last added brands. + + brands_modified - List of last modified brands. + + @TODO: Currently implementing only return of _all_ recipes. Extend the + recipe model to cater for this, and figure out modified stuff. + """ + + template_name = 'djangoshaker/index.html' + + def get_context_data(self, **kwargs): + """ + Overrides the default context data retrieval since we return multiple + contexts for the index view. + """ + + context = super(IndexView, self).get_context_data(**kwargs) + context['recipes_added'] = Recipe.latest_additions() + context['recipes_modified'] = Recipe.latest_changes() + context['brands_added'] = Brand.latest_additions() + context['brands_modified'] = Brand.latest_changes() + + return context + diff --git a/doc/TODO b/doc/TODO new file mode 100644 --- /dev/null +++ b/doc/TODO @@ -0,0 +1,30 @@ +* Features for version 0.2 +** User Presentation +*** TODO Implement a nice simple mobile-friendly CSS theme. +*** TODO Implement a nice simple desktop-friendly CSS theme. +*** TODO Implement nicer URL's for recipe etc. (normalise them, make them use names etc to be search-engine-friendly) + +* Features for version 0.1 +** User presentation +*** DONE Code-cleanup +**** DONE Reduce repetative code for templates, especially for detail views. +**** DONE Make templates use generic tags (for later purpose of CSS usage). +**** DONE In case of Recipe model, use the through keyword (https://docs.djangoproject.com/en/1.3/topics/db/models/#intermediary-manytomany) +*** DONE Index page +**** DONE Recently added recipes +**** DONE Recently modified recipes +**** DONE Recently added brands +**** DONE Recently modified brands +*** DONE Recipe list view +*** DONE Brand list view +*** DONE Ingredient list view +*** DONE Recipe view (with ingredients and quantities) +*** DONE Ingredient view (with list of brands) +*** DONE Brand view (with links to other brands representing the same ingredient) +*** DONE Measure view +*** DONE Measure list view +*** DONE Make the templates use the linebreaks filter for any long text. +** Back-end +*** DONE Integrate South (http://south.aeracode.org/) +*** DONE Add initial fixtures (sample files). + diff --git a/projtest/manage.py b/projtest/manage.py new file mode 100755 --- /dev/null +++ b/projtest/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "projtest.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/projtest/projtest/__init__.py b/projtest/projtest/__init__.py new file mode 100644 diff --git a/projtest/projtest/settings.py b/projtest/projtest/settings.py new file mode 100644 --- /dev/null +++ b/projtest/projtest/settings.py @@ -0,0 +1,153 @@ +# Django settings for projtest project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + ('Branko Majic', 'branko@majic.rs'), + # ('Your Name', 'your_email@example.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': 'projtest.db', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# In a Windows environment this must be set to your system time zone. +TIME_ZONE = 'Europe/Stockholm' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale. +USE_L10N = True + +# If you set this to False, Django will not use timezone-aware datetimes. +USE_TZ = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/home/media/media.lawrence.com/media/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash. +# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" +MEDIA_URL = '' + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = '' + +# URL prefix for static files. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = '/static/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +# 'django.contrib.staticfiles.finders.DefaultStorageFinder', +) + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '%uqs560g+sba+yjx!fo$9rrdiy31o%*#@#0^wg^05g0f4_7fl1' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + # Uncomment the next line for simple clickjacking protection: + # 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +ROOT_URLCONF = 'projtest.urls' + +# Python dotted path to the WSGI application used by Django's runserver. +WSGI_APPLICATION = 'projtest.wsgi.application' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + # Uncomment the next line to enable the admin: + 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', + 'djangoshaker', +) + +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error when DEBUG=False. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } +} diff --git a/projtest/projtest/urls.py b/projtest/projtest/urls.py new file mode 100644 --- /dev/null +++ b/projtest/projtest/urls.py @@ -0,0 +1,21 @@ +from django.conf.urls import patterns, include, url +from django.http import HttpResponseRedirect + +# Uncomment the next two lines to enable the admin: +from django.contrib import admin +admin.autodiscover() + +urlpatterns = patterns( + '', + # Examples: + # url(r'^$', 'projtest.views.home', name='home'), + # url(r'^projtest/', include('projtest.foo.urls')), + url(r'^$', lambda r : HttpResponseRedirect('shaker/')), + url(r'^shaker/', include('djangoshaker.urls')), + + # Uncomment the admin/doc line below to enable admin documentation: + # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), + + # Uncomment the next line to enable the admin: + url(r'^admin/', include(admin.site.urls)), +) diff --git a/projtest/projtest/wsgi.py b/projtest/projtest/wsgi.py new file mode 100644 --- /dev/null +++ b/projtest/projtest/wsgi.py @@ -0,0 +1,28 @@ +""" +WSGI config for projtest project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover +this application via the ``WSGI_APPLICATION`` setting. + +Usually you will have the standard Django WSGI application here, but it also +might make sense to replace the whole Django WSGI application with a custom one +that later delegates to the Django one. For example, you could introduce WSGI +middleware here, or combine a Django application with an application of another +framework. + +""" +import os + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "projtest.settings") + +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() + +# Apply WSGI middleware here. +# from helloworld.wsgi import HelloWorldApplication +# application = HelloWorldApplication(application)